swtpm: 0.10.1-unstable-05-06-2026 -> 0.10.1-unstable-2026-05-21

This commit is contained in:
Arthur Gautier 2026-05-26 11:19:30 -07:00
commit a3c6651274
2813 changed files with 72084 additions and 28593 deletions

View file

@ -1,6 +0,0 @@
<!--
Please note: This blank issue template is meant for extraordinary issues
that do not fit the templates. Unless you know your issue is relevant to
Nixpkgs and requires the free-form blank issue, please use the issue
templates instead.
-->

View file

@ -35,7 +35,8 @@ body:
If you are using an older version, please update to the latest stable version and check if the issue persists before continuing this bug report.
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -35,7 +35,8 @@ body:
If you are using an older version, please update to the latest stable version and check if the issue persists before continuing this bug report.
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -35,7 +35,8 @@ body:
If you are using an older version, please update to the latest stable version and check if the issue persists before continuing this bug report.
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -37,7 +37,8 @@ body:
If you are purposefully trying to build an ancient version of a package in an older Nixpkgs, please coordinate with the [NixOS Archivists](https://matrix.to/#/#archivists:nixos.org).
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -37,7 +37,8 @@ body:
If the package has been updated in unstable, but you believe the update should be backported to the stable release of Nixpkgs, please file the '**Request: backport to stable**' form instead.
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -35,7 +35,8 @@ body:
If you are using an older or stable version, please update to the latest **unstable** version and check if the module still does not exist before continuing this request.
options:
- "Please select a version."
- "- Unstable (26.05)"
- "- Unstable (26.11)"
- "- Beta (26.05)"
- "- Stable (25.11)"
default: 0
validations:

View file

@ -33,4 +33,15 @@
- maintainers/github-teams.json
- base-branch: ['master']
"backport release-26.05":
- all:
- changed-files:
- any-glob-to-any-file:
- .github/actions/**/*
- .github/workflows/*
- .github/labeler*.yml
- ci/**/*.*
- maintainers/github-teams.json
- base-branch: ['master']
# keep-sorted end

View file

@ -21,7 +21,7 @@ defaults:
jobs:
backport:
name: Backport Pull Request
if: vars.NIXPKGS_CI_APP_ID && github.event.pull_request.merged == true && (github.event.action != 'labeled' || startsWith(github.event.label.name, 'backport'))
if: vars.NIXPKGS_CI_CLIENT_ID && github.event.pull_request.merged == true && (github.event.action != 'labeled' || startsWith(github.event.label.name, 'backport'))
runs-on: ubuntu-slim
timeout-minutes: 3
steps:
@ -30,7 +30,7 @@ jobs:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write

View file

@ -57,10 +57,10 @@ jobs:
# Use a GitHub App, because it has much higher rate limits: 12,500 instead of 5,000 req / hour.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_APP_ID
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_CLIENT_ID
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-administration: read
permission-contents: write

View file

@ -31,10 +31,10 @@ jobs:
# Use the GitHub App to make sure the reaction happens with the same user who will later merge.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_APP_ID
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_CLIENT_ID
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-pull-requests: write

View file

@ -39,7 +39,7 @@ jobs:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-pull-requests: write

View file

@ -22,7 +22,7 @@ defaults:
jobs:
periodic-merge:
if: github.repository_owner == 'NixOS'
if: github.repository_owner == 'NixOS' || github.event_name == 'workflow_dispatch'
strategy:
# don't fail fast, so that all pairs are tried
fail-fast: false
@ -35,7 +35,7 @@ jobs:
into: staging-next-25.11
- from: staging-next-25.11
into: staging-25.11
- from: master
- from: release-26.05
into: staging-next-26.05
- from: staging-next-26.05
into: staging-26.05
@ -49,3 +49,34 @@ jobs:
name: ${{ matrix.pairs.name || format('{0} → {1}', matrix.pairs.from, matrix.pairs.into) }}
secrets:
NIXPKGS_CI_APP_PRIVATE_KEY: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
# Resets the target branch of the current haskell-updates PR.
# This makes GitHub hide all the commits that are already part of staging and gives us a much clearer PR view.
haskell-updates:
needs: periodic-merge
runs-on: ubuntu-slim
permissions:
pull-requests: write
steps:
- name: Find PR and update target branch
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
// There will at most be a single haskell-updates PR anyway, so no need to paginate.
await Promise.all(
(
await github.rest.pulls.list({
...context.repo,
state: 'open',
head: `${context.repo.owner}:haskell-updates`,
})
).data.map((pr) =>
github.rest.pulls.update({
...context.repo,
pull_number: pr.number,
// Just updating to the same branch to trigger a UI update.
// This is staging most of the time, but could be staging-next in rare cases.
base: pr.base.ref,
}),
),
)

View file

@ -22,7 +22,7 @@ defaults:
jobs:
periodic-merge:
if: github.repository_owner == 'NixOS'
if: github.repository_owner == 'NixOS' || github.event_name == 'workflow_dispatch'
strategy:
# don't fail fast, so that all pairs are tried
fail-fast: false

View file

@ -29,7 +29,7 @@ jobs:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write

View file

@ -28,10 +28,10 @@ jobs:
# Use the GitHub App to make sure the reaction happens with the same user who will later merge.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_APP_ID
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_CLIENT_ID
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-pull-requests: write

View file

@ -22,7 +22,7 @@ jobs:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
client-id: ${{ vars.NIXPKGS_CI_CLIENT_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-administration: read
permission-contents: write

View file

@ -124,6 +124,7 @@ nixos/modules/installer/tools/nix-fallback-paths.nix @Artturin @Ericson2314 @lo
# NixOS integration test driver
/nixos/lib/test-driver @tfc
/nixos/lib/testing @tfc
# NixOS QEMU virtualisation
/nixos/modules/virtualisation/qemu-vm.nix @raitobezarius

View file

@ -172,6 +172,7 @@ let
rebuildCountByKernel = lib.mapAttrs (
kernel: kernelRebuilds: lib.length kernelRebuilds
) rebuildsByKernel;
rebuildNames = extractPackageNames diffAttrs.rebuilds;
in
writeText "changed-paths.json" (
builtins.toJSON {
@ -188,7 +189,8 @@ let
kernel: rebuilds: lib.nameValuePair "10.rebuild-${kernel}-stdenv" (lib.elem "stdenv" rebuilds)
) rebuildsByKernel
// {
"10.rebuild-nixos-tests" = lib.elem "nixosTests.simple" (extractPackageNames diffAttrs.rebuilds);
"10.rebuild-nixos-tests" =
lib.elem "nixosTests.simple-container" rebuildNames || lib.elem "nixosTests.simple-vm" rebuildNames;
};
}
);

View file

@ -108,6 +108,8 @@ in
tweak (
(removeAttrs nixpkgsJobs blacklist)
// {
nixosTests = lib.filterAttrs (name: _: name == "simple") nixosJobs.tests;
nixosTests = lib.filterAttrs (
name: _: name == "simple-container" || name == "simple-vm"
) nixosJobs.tests;
}
)

View file

@ -99,7 +99,8 @@ async function checkTargetBranch({ github, context, core, dry }) {
...Object.values(changed.rebuildCountByKernel),
)
const rebuildsAllTests =
changed.attrdiff.changed.includes('nixosTests.simple')
changed.attrdiff.changed.includes('nixosTests.simple-container') ||
changed.attrdiff.changed.includes('nixosTests.simple-vm')
// https://github.com/NixOS/nixpkgs/pull/481205#issuecomment-3790123921
// These should go to staging-nixos instead of master,

View file

@ -48,6 +48,7 @@ unzip.section.md
validatePkgConfig.section.md
versionCheckHook.section.md
waf.section.md
writable-tmpdir-as-home-hook.section.md
zig.section.md
xcbuild.section.md
xfce4-dev-tools.section.md

View file

@ -0,0 +1,5 @@
# writableTmpDirAsHomeHook {#writableTmpDirAsHomeHook}
This setup hook provides a writable home directory for packages that require it.
To use, just add the hook to the `nativeBuildInputs` of the package.

View file

@ -342,12 +342,54 @@ stdenv.mkDerivation (finalAttrs: {
pnpmDeps = fetchPnpmDeps {
inherit (finalAttrs) pname version src;
inherit pnpm;
fetcherVersion = 3;
fetcherVersion = 4;
hash = "...";
};
})
```
It is highly recommended to use a pinned version of pnpm (i.e., `pnpm_9` or `pnpm_10`), to increase future reproducibility. It might also be required to use an older version if the package needs support for a certain lock file version. To do so, you can pass the `pnpm` argument to `fetchPnpmDeps` and override the `pnpm` arg in `pnpmConfigHook`. Here are the changes in the example above to use a pinned pnpm version:
<!-- TODO: Does splicing still work when overriding in nativeBuildInputs here? -->
```diff
{
fetchPnpmDeps,
nodejs,
- pnpm,
+ pnpm_10,
pnpmConfigHook,
stdenv,
}:
+let
+ # Optionally override pnpm to use a custom nodejs version
+ # Make sure that the same nodejs version is referenced in nativeBuildInputs
+ # pnpm = pnpm_10.override { nodejs = nodejs-slim_22; };
+in
stdenv.mkDerivation (finalAttrs: {
pname = "foo";
version = "0-unstable-1980-01-01";
src = {
#...
};
nativeBuildInputs = [
nodejs # in case scripts are run outside of a pnpm call
pnpmConfigHook
- pnpm # At least required by pnpmConfigHook, if not other (custom) phases
+ pnpm_10 # At least required by pnpmConfigHook, if not other (custom) phases
];
pnpmDeps = fetchPnpmDeps {
inherit (finalAttrs) pname version src;
+ pnpm = pnpm_10;
fetcherVersion = 4;
hash = "...";
};
})
```
In case you are patching `package.json` or `pnpm-lock.yaml`, make sure to pass `finalAttrs.patches` to the function as well (i.e., `inherit (finalAttrs) patches`.
`pnpmConfigHook` supports adding additional `pnpm install` flags via `pnpmInstallFlags` which can be set to a Nix string array:
@ -456,7 +498,7 @@ This is the version of the output of `fetchPnpmDeps`. New packages should use `3
# ...
pnpmDeps = fetchPnpmDeps {
# ...
fetcherVersion = 3;
fetcherVersion = 4;
hash = "..."; # clear this hash and generate a new one
};
}
@ -474,6 +516,7 @@ Version 3 is the recommended value for new packages. Versions 1 and 2 are deprec
- 1: Initial version, nothing special.
- 2: [Ensure consistent permissions](https://github.com/NixOS/nixpkgs/pull/422975)
- 3: [Build a reproducible tarball](https://github.com/NixOS/nixpkgs/pull/469950)
- 4: [Dump SQLite database to an SQL file](https://github.com/NixOS/nixpkgs/pull/522703)
### Yarn {#javascript-yarn}

View file

@ -232,7 +232,7 @@ The following is an example:
vyp
lblasc
];
license.fullName = "MIT/X11";
license = lib.licenses.mit;
};
};
}

View file

@ -871,6 +871,15 @@ general. A number of other parameters can be overridden:
(hello { }).override { extraRustcOpts = "-Z debuginfo=2"; }
```
- Extra arguments passed to `rustc` when the crate is a proc-macro,
replacing `extraRustcOpts`. Useful to keep instrumentation flags
(sanitizers, coverage) off host dylibs. Defaults to `null`, which
inherits `extraRustcOpts`:
```nix
(myProcMacro { }).override { extraRustcOptsForProcMacro = [ ]; }
```
- The lint level cap passed to `rustc`. Defaults to `null`, which
auto-resolves to `"allow"` (silences all lints) when `lints` is
empty, or `"forbid"` (no cap) when `lints` is set. Because `rustc`

View file

@ -2,9 +2,7 @@
There is a TeX Live packaging that lives entirely under attribute `texlive`.
## User's guide (experimental new interface) {#sec-language-texlive-user-guide-experimental}
Release 23.11 ships with a new interface that will eventually replace `texlive.combine`.
## User's guide {#sec-language-texlive-user-guide}
- For basic usage, use some of the prebuilt environments available at the top level, such as `texliveBasic`, `texliveSmall`. For the full list of prebuilt environments, inspect `texlive.schemes`.
@ -24,7 +22,7 @@ Release 23.11 ships with a new interface that will eventually replace `texlive.c
- `texlive.withPackages` uses the same logic as `buildEnv`. Only parts of a package are installed in an environment: its 'runtime' files (`tex` output), binaries (`out` output), and support files (`tlpkg` output). Moreover, man and info pages are assembled into separate `man` and `info` outputs. To add only the TeX files of a package, or its documentation (`texdoc` output), just specify the outputs:
```nix
texlive.withPackages (
texliveBasic.withPackages (
ps: with ps; [
texdoc # recommended package to navigate the documentation
perlPackages.LaTeXML.tex # tex files of LaTeXML, omit binaries
@ -34,64 +32,19 @@ Release 23.11 ships with a new interface that will eventually replace `texlive.c
)
```
- To add the documentation for all packages in the environment, use
```nix
texliveSmall.overrideAttrs { withDocs = true; }
```
This can be applied before or after calling `withPackages`. The parameter `withSources` adds all source containers.
- All packages distributed by TeX Live, which contains most of CTAN, are available and can be found under `texlive.pkgs`:
```ShellSession
$ nix repl
nix-repl> :l <nixpkgs>
nix-repl> texlive.pkgs.[TAB]
```
Note that the packages in `texlive.pkgs` are only provided for search purposes and must not be used directly.
- **Experimental and subject to change without notice:** to add the documentation for all packages in the environment, use
```nix
texliveSmall.__overrideTeXConfig { withDocs = true; }
```
This can be applied before or after calling `withPackages`.
The function currently supports the parameters `withDocs`, `withSources`, and `requireTeXPackages`.
## User's guide {#sec-language-texlive-user-guide}
- For basic usage just pull `texlive.combined.scheme-basic` for an environment with basic LaTeX support.
- It typically won't work to use separately installed packages together. Instead, you can build a custom set of packages like this. Most CTAN packages should be available:
```nix
texlive.combine {
inherit (texlive)
scheme-small
collection-langkorean
algorithms
cm-super
;
}
```
- There are all the schemes, collections and a few thousand packages, as defined upstream (perhaps with tiny differences).
- By default you only get executables and files needed during runtime, and a little documentation for the core packages. To change that, you need to add `pkgFilter` function to `combine`.
```nix
texlive.combine {
# inherit (texlive) whatever-you-want;
pkgFilter =
pkg: pkg.tlType == "run" || pkg.tlType == "bin" || pkg.hasManpages || pkg.pname == "cm-super";
# elem tlType [ "run" "bin" "doc" "source" ]
# there are also other attributes: version, name
}
```
- You can list packages e.g. by `nix repl`.
```ShellSession
$ nix repl
nix-repl> :l <nixpkgs>
nix-repl> texlive.collection-[TAB]
```
- Note that the wrapper assumes that the result has a chance to be useful. For example, the core executables should be present, as well as some core data files. The supported way of ensuring this is by including some scheme, for example, `scheme-basic`, into the combination.
- TeX Live packages are also available under `texlive.pkgs` as derivations with outputs `out`, `tex`, `texdoc`, `texsource`, `tlpkg`, `man`, `info`. They cannot be installed outside of `texlive.combine` but are available for other uses. To repackage a font, for instance, use
These are derivations with outputs `out`, `tex`, `texdoc`, `texsource`, `tlpkg`, `man`, `info`. They cannot be installed outside of `texlive.withPackages` but are available for other uses. To repackage a font, for instance, use
```nix
stdenvNoCC.mkDerivation (finalAttrs: {
@ -112,9 +65,9 @@ Release 23.11 ships with a new interface that will eventually replace `texlive.c
## Custom packages {#sec-language-texlive-custom-packages}
You may find that you need to use an external TeX package. A derivation for such package has to provide the contents of the "texmf" directory in its `"tex"` output, according to the [TeX Directory Structure](https://tug.ctan.org/tds/tds.html). Dependencies on other TeX packages can be listed in the attribute `tlDeps`.
You may find that you need to use an external TeX package. A derivation for such package has to provide the contents of the "texmf" directory in its `"tex"` output, according to the [TeX Directory Structure](https://tug.ctan.org/tds/tds.html). Dependencies on other TeX packages can be listed in the attribute `passthru.tlDeps`, which is a function taking a package set and returning a list of packages.
The functions `texlive.combine` and `texlive.withPackages` recognise the following outputs:
The function `texlive.withPackages` recognise the following outputs:
- `"out"`: contents are linked in the TeX Live environment, and binaries in the `$out/bin` folder are wrapped;
- `"tex"`: linked in `$TEXMFDIST`; files should follow the TDS (for instance `$tex/tex/latex/foiltex/foiltex.cls`);
@ -122,8 +75,6 @@ The functions `texlive.combine` and `texlive.withPackages` recognise the followi
- `"tlpkg"`: linked in `$TEXMFROOT/tlpkg`;
- `"man"`, `"info"`, ...: the other outputs are combined into separate outputs.
When using `pkgFilter`, `texlive.combine` will assign `tlType` respectively `"bin"`, `"run"`, `"doc"`, `"source"`, `"tlpkg"` to the above outputs.
Here is a (very verbose) example. See also the packages `auctex`, `eukleides`, `mftrace` for more examples.
```nix
@ -138,7 +89,7 @@ let
"tex"
"texdoc"
];
passthru.tlDeps = with texlive; [ latex ];
passthru.tlDeps = ps: [ ps.latex ];
srcs = [
(fetchurl {
@ -169,13 +120,14 @@ let
latexmk
]
))
# multiple-outputs.sh fails if $out is not defined
(writeShellScript "force-tex-output.sh" ''
out="''${tex-}"
'')
writableTmpDirAsHomeHook # Need a writable $HOME for latexmk
];
# multiple-outputs.sh fails if $out is not defined
preHook = ''
out="''${tex-}"
'';
dontConfigure = true;
buildPhase = ''

View file

@ -433,6 +433,30 @@
"chap-overlays": [
"index.html#chap-overlays"
],
"sec-nixpkgs-release-26.11": [
"release-notes.html#sec-nixpkgs-release-26.11"
],
"sec-nixpkgs-release-26.11-highlights": [
"release-notes.html#sec-nixpkgs-release-26.11-highlights"
],
"sec-nixpkgs-release-26.11-incompatibilities": [
"release-notes.html#sec-nixpkgs-release-26.11-incompatibilities"
],
"sec-nixpkgs-release-26.11-lib": [
"release-notes.html#sec-nixpkgs-release-26.11-lib"
],
"sec-nixpkgs-release-26.11-lib-breaking": [
"release-notes.html#sec-nixpkgs-release-26.11-lib-breaking"
],
"sec-nixpkgs-release-26.11-lib-deprecations": [
"release-notes.html#sec-nixpkgs-release-26.11-lib-deprecations"
],
"sec-nixpkgs-release-26.11-lib-additions-improvements": [
"release-notes.html#sec-nixpkgs-release-26.11-lib-additions-improvements"
],
"sec-nixpkgs-release-26.11-notable-changes": [
"release-notes.html#sec-nixpkgs-release-26.11-notable-changes"
],
"sec-nixpkgs-release-26.05": [
"release-notes.html#sec-nixpkgs-release-26.05"
],
@ -1016,6 +1040,9 @@
"tar-files": [
"index.html#tar-files"
],
"writableTmpDirAsHomeHook": [
"index.html#writableTmpDirAsHomeHook"
],
"x86_64-darwin-26.05": [
"release-notes.html#x86_64-darwin-26.05"
],
@ -4428,11 +4455,9 @@
"sec-language-texlive": [
"index.html#sec-language-texlive"
],
"sec-language-texlive-user-guide-experimental": [
"index.html#sec-language-texlive-user-guide-experimental"
],
"sec-language-texlive-user-guide": [
"index.html#sec-language-texlive-user-guide"
"index.html#sec-language-texlive-user-guide",
"index.html#sec-language-texlive-user-guide-experimental"
],
"sec-language-texlive-custom-packages": [
"index.html#sec-language-texlive-custom-packages"

View file

@ -3,6 +3,7 @@
This section lists the release notes for each stable version of Nixpkgs and the current unstable revision.
```{=include=} sections
rl-2611.section.md
rl-2605.section.md
rl-2511.section.md
rl-2505.section.md

View file

@ -253,6 +253,8 @@
- Ethercalc and its associated module have been removed, as the package is unmaintained and cannot be installed from source with npm now.
- The `services.avahi.wideArea` option now defaults to `false` as a mitigation against [`CVE-2024-52615`/`GHSA-x6vp-f33h-h32g`](https://github.com/avahi/avahi/security/advisories/GHSA-x6vp-f33h-h32g).
- `coreth` has been removed, as upstream has moved it into `avalanchego`.
- `nodePackages.prebuild-install` was removed because it appeared to be unmaintained upstream.
@ -302,6 +304,8 @@
- The `pie` hardening flag has been removed and will now error, after being deprecated in 25.11. Compilers are expected to enable PIE by default, as has been common practice since 2016 outside of Nixpkgs. If a package needs `pie` disabled pass `-no-pie` in `CFLAGS`. It is unlikely this will be necessary in many cases; due to the prevalence of default PIE toolchains, most packages incompatible with PIE already pass `-no-pie`.
- `pqos-wrapper` was removed as it has been unmaintained since 2022 and not widely used.
## Other Notable Changes {#sec-nixpkgs-release-26.05-notable-changes}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@ -362,6 +366,8 @@
- We now use the upstream wrapper script for Gradle, supporting both the `JAVA_HOME` and `GRADLE_OPTS` environment variables.
- Updated `gonic` to 0.21.0. A full ("slow") scan is recommended after upgrading to v0.21.0 to pick up the newly scanned fields (contributors, ISRCs, record labels, per-track years, ARTIST_CREDIT).
- the `autossh-ng` NixOS module was introduced as a simpler alternative to the existing `autossh` module.
- Added `haskell.packages.microhs`, a set of Haskell packages built with MicroHs.
@ -390,7 +396,7 @@ gnuradioMinimal.override {
### Breaking changes {#sec-nixpkgs-release-26.05-lib-breaking}
- The `nodejs_latest` alias now points to `nodejs_25` instead of `nodejs_24`.
- The `nodejs_latest` alias now points to `nodejs_26` instead of `nodejs_24`.
- `nodejs-slim` no longer exposes a `corepack` executable, it has been moved to an ad-hoc output; to restore the previous behavior, `nodejs-slim.corepack` must be explicitly included.

View file

@ -1,4 +1,4 @@
# Nixpkgs 26.11 (2026.11/??) {#sec-nixpkgs-release-26.11}
# Nixpkgs 26.11 ("Zokor", 2026.11/??) {#sec-nixpkgs-release-26.11}
## Highlights {#sec-nixpkgs-release-26.11-highlights}
@ -10,9 +10,7 @@
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- `pqos-wrapper` was removed
as it has been unmaintained since 2022
and not widely used.
- `hurl` has been updated to `8.x.x` which has some breaking changes. See [upstream changelog](https://github.com/Orange-OpenSource/hurl/releases/tag/8.0.0) for details.
## Other Notable Changes {#sec-nixpkgs-release-26.11-notable-changes}

View file

@ -1 +1 @@
26.05
26.11

View file

@ -30,7 +30,6 @@ let
flatten
deepSeq
extends
toFunction
id
;
inherit (lib.strings) levenshtein levenshteinAtMost;
@ -842,14 +841,6 @@ rec {
:::
*/
extendMkDerivation =
let
extendsWithExclusion =
excludedNames: g: f: final:
let
previous = f final;
in
removeAttrs previous excludedNames // g final previous;
in
{
constructDrv,
excludeDrvArgNames ? [ ],
@ -858,24 +849,27 @@ rec {
inheritFunctionArgs ? true,
transformDrv ? id,
}:
setFunctionArgs
{
# Adds the fixed-point style support
(
fpargs:
__functor =
self: fpargs:
transformDrv (
constructDrv (extendsWithExclusion excludeDrvArgNames extendDrvArgs (toFunction fpargs))
)
)
# Add __functionArgs
(
removeAttrs (
# Inherit the __functionArgs from the base build helper
optionalAttrs inheritFunctionArgs (removeAttrs (functionArgs constructDrv) excludeDrvArgNames)
# Recover the __functionArgs from the derived build helper
// functionArgs (extendDrvArgs { })
) excludeFunctionArgNames
)
// {
constructDrv (
final:
let
previous = if isFunction fpargs then fpargs final else fpargs;
in
removeAttrs previous excludeDrvArgNames // extendDrvArgs final previous
)
);
__functionArgs = removeAttrs (
# Inherit the __functionArgs from the base build helper
optionalAttrs inheritFunctionArgs (removeAttrs (functionArgs constructDrv) excludeDrvArgNames)
# Recover the __functionArgs from the derived build helper
// functionArgs (extendDrvArgs { })
) excludeFunctionArgNames;
inherit
# Expose to the result build helper.
constructDrv

View file

@ -1,16 +1,38 @@
# snippets that can be shared by multiple fetchers (pkgs/build-support)
{ lib }:
let
commonH = hashTypes: rec {
hashNames = [ "hash" ] ++ hashTypes;
hashSet = lib.genAttrs hashNames (lib.const { });
};
commonH =
let
defaultHashNames = [ "hash" ];
in
hashTypes: rec {
hashNames = defaultHashNames ++ hashTypes;
hashSet = genAttrs hashNames (const { });
};
fakeH = {
hash = lib.fakeHash;
sha256 = lib.fakeSha256;
sha512 = lib.fakeSha512;
};
defaultHashTypes = [ "sha256" ];
inherit (lib)
concatMapStringsSep
head
length
throwIf
;
inherit (lib.attrsets)
attrsToList
intersectAttrs
genAttrs
removeAttrs
optionalAttrs
;
inherit (lib.trivial) const functionArgs setFunctionArgs;
in
rec {
@ -90,27 +112,14 @@ rec {
*/
normalizeHash =
{
hashTypes ? [ "sha256" ],
hashTypes ? defaultHashTypes,
required ? true,
}:
let
inherit (lib)
concatMapStringsSep
head
tail
throwIf
;
inherit (lib.attrsets)
attrsToList
intersectAttrs
removeAttrs
optionalAttrs
;
inherit (commonH hashTypes) hashNames hashSet;
in
args:
if args ? "outputHash" then
if args ? outputHash then
args
else
let
@ -122,7 +131,7 @@ rec {
in
if hashesAsNVPairs == [ ] then
throwIf required "fetcher called without `hash`" null
else if tail hashesAsNVPairs != [ ] then
else if length hashesAsNVPairs != 1 then
throw "fetcher called with mutually-incompatible arguments: ${
concatMapStringsSep ", " (a: a.name) hashesAsNVPairs
}"
@ -190,15 +199,20 @@ rec {
and is implemented somewhat more efficiently.
*/
withNormalizedHash =
let
removedAttributes = [
"outputHash"
"outputHashAlgo"
];
in
{
hashTypes ? [ "sha256" ],
hashTypes ? defaultHashTypes,
}:
let
inherit (commonH hashTypes) hashSet;
in
fetcher:
let
inherit (lib.attrsets) intersectAttrs removeAttrs;
inherit (lib.trivial) functionArgs setFunctionArgs;
inherit (commonH hashTypes) hashSet;
fArgs = functionArgs fetcher;
normalize = normalizeHash {
@ -211,10 +225,7 @@ rec {
assert intersectAttrs fArgs hashSet == { };
setFunctionArgs (args: fetcher (normalize args)) (
removeAttrs fArgs [
"outputHash"
"outputHashAlgo"
]
removeAttrs fArgs removedAttributes
// {
hash = fArgs.outputHash;
}

View file

@ -1,4 +1,23 @@
{ lib }:
let
inherit (lib) all any elem;
handleComplexProperty =
evaluateSubProperty: AND: OR: license:
if license.licenseType == "compound" then
if license.operator == "OR" then
OR evaluateSubProperty license.licenses
else if license.operator == "AND" then
AND evaluateSubProperty license.licenses
else
throw "Unknown license operator"
else if license.licenseType == "exception" then
evaluateSubProperty license.license && evaluateSubProperty license.exception
else if license.licenseType == "plus" then
evaluateSubProperty license.license
else
throw "Unknown license type or legacy license";
in
rec {
/**
Evaluate a license expression for a given predicate.
@ -21,29 +40,45 @@ rec {
- [license] license expression to check
*/
evaluateProperty =
predicate: permissive: license:
predicate: permissive:
let
OR = if permissive then lib.any else lib.all;
AND = if permissive then lib.all else lib.any;
OR = if permissive then any else all;
AND = if permissive then all else any;
evaluateComplexProperty = handleComplexProperty (evaluateProperty predicate permissive) AND OR;
in
if license.licenseType == "simple" then
predicate license
else if license.licenseType == "compound" then
if license.operator == "OR" then
OR (x: evaluateProperty predicate permissive x) license.licenses
else if license.operator == "AND" then
AND (x: evaluateProperty predicate permissive x) license.licenses
else
throw "Unknown license operator"
else if license.licenseType == "exception" then
AND (x: evaluateProperty predicate permissive x) [
license.license
license.exception
]
else if license.licenseType == "plus" then
evaluateProperty predicate permissive license.license
else
throw "Unknown license type or legacy license";
license:
if license.licenseType == "simple" then predicate license else evaluateComplexProperty license;
/**
Evaluate a license expression for a given property name. The property must
be defined as a boolean attribute of all licenses passed.
# Example
```nix
evaluateNamedProperty "deprecated" true (with lib.licenses; AND [ ncsa (WITH asl20 llvm-exception) ])
```
# Type
```
evaluateProperty :: String -> Bool -> AttrSet -> Bool
```
# Arguments
- [name] name of the attribute to check
- [permissive] whether to apply checks permissive or reciprocal
- [license] license expression to check
*/
evaluateNamedProperty =
name: permissive:
let
OR = if permissive then any else all;
AND = if permissive then all else any;
evaluateComplexProperty = handleComplexProperty (evaluateNamedProperty name permissive) AND OR;
in
license:
if license.licenseType == "simple" then license.${name} else evaluateComplexProperty license;
/**
Check whether a license expression is free.
@ -65,7 +100,7 @@ rec {
- [license] License expression to check if free
*/
isFree = evaluateProperty (x: x.free) true;
isFree = evaluateNamedProperty "free" true;
/**
Check whether a license expression is redistributable.
@ -87,7 +122,7 @@ rec {
- [license] License expression to check if redistributable
*/
isRedistributable = evaluateProperty (x: x.redistributable) true;
isRedistributable = evaluateNamedProperty "redistributable" true;
/**
Check whether any of the given licenses is required in the license expression.
@ -110,7 +145,7 @@ rec {
- [licenses] List of licenses to look
- [license] License expression to check
*/
containsLicenses = licenses: evaluateProperty (x: lib.lists.elem x licenses) false;
containsLicenses = licenses: evaluateProperty (x: elem x licenses) false;
/**
Convert a license expression to an SPDX license expression string.

View file

@ -1598,6 +1598,28 @@ let
inherit priority content;
};
/**
Applies a function to the value inside a definition,
preserving all surrounding properties (`mkForce`, `mkOrder`, `mkIf`, etc.).
*/
mapDefinitionValue =
f: def:
if def ? _type then
if def._type == "merge" then
def // { contents = map (mapDefinitionValue f) def.contents; }
else if def._type == "if" then
def // { content = mapDefinitionValue f def.content; }
else if def._type == "override" then
def // { content = mapDefinitionValue f def.content; }
else if def._type == "order" then
def // { content = mapDefinitionValue f def.content; }
else if def._type == "definition" then
def // { value = mapDefinitionValue f def.value; }
else
f def
else
f def;
mkBefore = mkOrder 500;
defaultOrderPriority = 1000;
mkAfter = mkOrder 1500;
@ -2302,6 +2324,7 @@ private
importApply
importJSON
importTOML
mapDefinitionValue
mergeDefinitions
mergeAttrDefinitionsWithPrio
mergeOptionDecls # should be private?

View file

@ -7,12 +7,19 @@ let
match
split
storeDir
escapeRegex
removePrefix
;
inherit (lib)
boolToString
filter
isString
readFile
concatStrings
length
elemAt
isList
any
;
inherit (lib.filesystem)
pathIsRegularFile
@ -513,6 +520,113 @@ let
else
throw "repoRevToName: invalid kind";
/**
Filter a source tree by a list of doublestar-style glob patterns,
returning a source that only contains paths matching at least one
pattern. `*` matches a single path component, and `**` matches any
number of components.
# Inputs
`src`
: The source tree to filter.
`patterns`
: List of glob patterns to include, e.g. `[ "*.py" "src/**" ]`.
A leading `**` (e.g. `**\/*.py` for all `.py` files at any depth)
is also supported; the `\` here is just a Nix string escape used
to avoid closing this comment.
# Examples
:::{.example}
## `sourceByGlobs` usage example
- Include everything under a subdirectory
```nix
src = sourceByGlobs ./. [ "src/**" "tests/**" ]
```
- Include all .py files in root directory only
```nix
src = sourceByGlobs ./. [ "*.py" ]
```
:::
*/
sourceByGlobs =
let
splitPath = path: filter isString (split "/" path);
# Make component regex
mkRe =
s:
if s == "**" then
".*" # Has special handling below
else
concatStrings (map (tok: if isList tok then "[^/]*" else escapeRegex tok) (split "\\*+" s));
# Make a source filter function from pattern
mkMatcher =
pat:
let
globs = map mkRe (splitPath pat);
glen = length globs;
in
path: type:
let
path' = splitPath path;
plen = length path';
recurse =
gi: pi:
let
g = elemAt globs gi;
p = elemAt path' pi;
m = match g p != null;
in
if pi >= plen then # Reached end of path
gi >= glen || (type == "directory" || type == "symlink") # Only allow partial matches for directories
else if gi >= glen then # Reached end of globs
false
else if g == ".*" then # Special handling for **
(
# Lookahead for next glob match
if (gi + 1) == glen then
true
else if (match (elemAt globs (gi + 1)) p != null) then
recurse (gi + 1) pi
else if m then
recurse gi (pi + 1)
else
false
)
else if m then
recurse (gi + 1) (pi + 1)
else
false;
in
recurse 0 0;
mkSourceFilter =
root: patterns:
let
root' = "${toString root}/";
matchers = map mkMatcher patterns;
in
name: type:
let
name' = removePrefix root' name;
in
any (m: m name' type) matchers;
in
src: patterns:
lib.cleanSourceWith {
filter = mkSourceFilter src patterns;
inherit src;
};
in
{
inherit
@ -532,6 +646,7 @@ in
sourceByRegex
sourceFilesBySuffices
sourceByGlobs
trace
;

View file

@ -5114,4 +5114,96 @@ runTests {
);
expected = false;
};
# mapDefinitionValue
testMapDefinitionValuePlain = {
expr = lib.modules.mapDefinitionValue (x: x + 1) 5;
expected = 6;
};
testMapDefinitionValueMkForce = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkForce 5);
expected = lib.mkForce 6;
};
testMapDefinitionValueMkDefault = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkDefault 5);
expected = lib.mkDefault 6;
};
testMapDefinitionValueMkOrder = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkOrder 500 5);
expected = lib.mkOrder 500 6;
};
testMapDefinitionValueMkOverrideNested = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkForce (lib.mkOrder 500 5));
expected = lib.mkForce (lib.mkOrder 500 6);
};
testMapDefinitionValueMkIf = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkIf true 5);
expected = lib.mkIf true 6;
};
testMapDefinitionValueMkMerge = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (
lib.mkMerge [
5
10
]
);
expected = lib.mkMerge [
6
11
];
};
testMapDefinitionValueMkDefinition = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (
lib.mkDefinition {
file = "test";
value = 5;
}
);
expected = lib.mkDefinition {
file = "test";
value = 6;
};
};
testMapDefinitionValueDeep = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (lib.mkIf true (lib.mkForce (lib.mkOrder 500 5)));
expected = lib.mkIf true (lib.mkForce (lib.mkOrder 500 6));
};
testMapDefinitionValueAllNested = {
expr = lib.modules.mapDefinitionValue (x: x + 1) (
lib.mkMerge [
(lib.mkIf true (
lib.mkForce (
lib.mkOrder 500 (
lib.mkDefinition {
file = "test";
value = lib.mkBefore 5;
}
)
)
))
]
);
expected = lib.mkMerge [
(lib.mkIf true (
lib.mkForce (
lib.mkOrder 500 (
lib.mkDefinition {
file = "test";
value = lib.mkBefore 6;
}
)
)
))
];
};
}

View file

@ -899,6 +899,19 @@ checkConfigError 'Did you mean .enable., .ebe. or .enabled.\?' config ./error-ty
checkConfigError 'Did you mean .services\.myservice\.port. or .services\.myservice\.enable.\?' config.services.myservice ./error-typo-submodule.nix
checkConfigError 'Did you mean .services\.nginx\.virtualHosts\."example\.com"\.ssl\.certificate. or .services\.nginx\.virtualHosts\."example\.com"\.ssl\.certificateKey.\?' config.services.nginx.virtualHosts.\"example.com\" ./error-typo-deeply-nested.nix
# types.attrListOf
checkConfigOutput '"ok"' config.assertions ./declare-attrList.nix
checkConfigError 'A definition for option .attrListInt.badValue.a. is not of type .signed integer.. Definition values:' config.attrListIntStrict.badValue ./declare-attrList.nix
checkConfigError 'A definition for option .attrList.badListElem. is not of type .attribute list of string.. Each list element must be a single-key attribute set, but got 2 keys' config.attrListStrict.badListElem ./declare-attrList.nix
checkConfigError 'A definition for option .attrList.badString. is not of type .attribute list of string.. TypeError: Definition values:' config.attrListStrict.badString ./declare-attrList.nix
checkConfigError 'A definition for option .attrList.badListString. is not of type .attribute list of string.. Each list element must be an attribute set, but got string' config.attrListStrict.badListString ./declare-attrList.nix
# attrListWith valueMeta.definitions: file propagation
checkConfigError 'the-defs-file\.nix' config.argv ./attrList-valueMeta-definitions-file-diagnostic-forwarding.nix
# attrListOf does not support type merging
checkConfigError 'The option .merged. in .*/declare-attrList-type-merge.nix. is already declared in .*/declare-attrList-type-merge.nix' config.merged ./declare-attrList-type-merge.nix
cat <<EOF
====== module tests ======
$pass Pass

View file

@ -0,0 +1,25 @@
{ lib, options, ... }:
let
inherit (lib) mkOption mkMerge types;
in
{
imports = [
{
_file = "the-defs-file.nix";
config.flags.my-flag = 3.14;
}
];
options.flags = mkOption {
type = types.attrListWith {
elemType = types.anything;
asAttrs = true;
mergeAttrValues = _name: vs: lib.head vs;
};
};
options.argv = mkOption { type = types.listOf types.str; };
# Feed definitions into argv; the float from the-defs-file.nix should cause
# a type error mentioning that file
config.argv = mkMerge options.flags.valueMeta.definitions;
}

View file

@ -0,0 +1,12 @@
# Test that attrListOf does not support type merging:
# two declarations of the same option should fail.
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
imports = [
{ options.merged = mkOption { type = types.attrListOf types.str; }; }
{ options.merged = mkOption { type = types.attrListOf types.str; }; }
];
}

View file

@ -0,0 +1,925 @@
# Run with:
# cd nixpkgs
# ./lib/tests/modules.sh
{ lib, config, ... }:
let
inherit (lib)
mkOption
mkOrder
mkMerge
mkBefore
mkAfter
mkIf
mkOverride
mkDefault
mkForce
types
;
in
{
options = {
attrList = mkOption {
type = types.lazyAttrsOf (types.attrListOf types.str);
};
attrListInt = mkOption {
type = types.lazyAttrsOf (types.attrListOf types.int);
};
attrListSubmodule = mkOption {
type = types.attrListOf (
types.submodule {
options.port = mkOption {
type = types.int;
description = "Port number";
};
options.host = mkOption {
type = types.str;
default = "localhost";
description = "Hostname";
};
}
);
};
# asAttrs: value is a merged attrset, ordered list in valueMeta
asAttrs = mkOption {
type = types.lazyAttrsOf (
types.attrListWith {
elemType = types.str;
asAttrs = true;
mergeAttrValues = _name: values: lib.last values;
}
);
};
# asAttrs with default mergeAttrValues: duplicates collected into lists
asAttrsDefault = mkOption {
type = types.lazyAttrsOf (
types.attrListWith {
elemType = types.int;
asAttrs = true;
}
);
};
# Strict wrappers that force deep evaluation, for testing error cases
attrListStrict = mkOption {
type = types.lazyAttrsOf types.raw;
};
attrListIntStrict = mkOption {
type = types.lazyAttrsOf types.raw;
};
# either picks attrList when input is list/attrset, int when input is int
eitherAttrListOrInt = mkOption {
type = types.either (types.attrListOf types.str) types.int;
};
eitherAttrListOrIntFallback = mkOption {
type = types.either (types.attrListOf types.str) types.int;
};
eitherIntOrAttrList = mkOption {
type = types.either types.int (types.attrListOf types.str);
};
eitherIntOrAttrListFallback = mkOption {
type = types.either types.int (types.attrListOf types.str);
};
assertions = mkOption { };
};
imports = [
# Second module contributing to multiModule
{
attrListInt.multiModule = [
{ b = 2; }
];
}
];
config = {
# List input: pass-through
attrList.listInput = [
{ a = "alpha"; }
{ b = "beta"; }
];
# Attrset input with explicit ordering
attrList.attrsetOrdered = {
x = mkOrder 200 "x-val";
y = mkOrder 100 "y-val";
};
# Mixed: list elements at default priority, attrset with mkOrder
attrList.mixed = mkMerge [
[
{ m = "from-list"; }
]
{
n = mkOrder 50 "from-attrset";
}
];
# Multiple list definitions from separate modules
attrListInt.multiModule = [
{ a = 1; }
];
# Attrset without mkOrder uses default priority
attrList.attrsetNoOrder = {
foo = "bar";
baz = "qux";
};
# Empty list
attrList.empty = [ ];
# Ordering test: lower priority first
attrList.ordering = mkMerge [
{
last = mkOrder 1500 "last";
}
{
first = mkOrder 500 "first";
}
[
{ middle = "middle"; }
]
];
# List elements support mkOrder/mkBefore/mkAfter
attrList.listOrdering = [
(mkAfter { z = "after"; })
{ m = "default"; }
(mkBefore { a = "before"; })
];
# Plain list entries land at default priority (1000):
# they appear after mkOrder 999 and before mkOrder 1001.
attrList.listDefaultPrio = mkMerge [
{ after = mkOrder 1001 "after"; }
[
{ mid = "list-entry"; }
]
{ before = mkOrder 999 "before"; }
];
# mkBefore and mkAfter
attrList.beforeAfter = mkMerge [
{
z = mkAfter "after";
}
{
a = mkBefore "before";
}
[
{ m = "default"; }
]
];
# mkIf: conditional definition
attrList.withMkIf = mkMerge [
(mkIf true [
{ yes = "included"; }
])
(mkIf false [
{ no = "excluded"; }
])
];
# mkOverride: higher priority override wins
attrList.withOverride = mkMerge [
(mkOverride 100 [
{ replaced = "gone"; }
])
(mkOverride 50 [
{ winner = "wins"; }
])
];
# mkDefault: lower priority than normal
attrList.withDefault = mkMerge [
(mkDefault [
{ default = "overridden"; }
])
[
{ normal = "wins"; }
]
];
# mkForce on the whole option (should discard other defs)
attrList.withForce = mkMerge [
[
{ discarded = "gone"; }
]
(mkForce [
{ forced = "wins"; }
])
];
# mkForce with mkOrder inside
attrList.forceWithOrder = mkForce [
(mkAfter { second = "after"; })
(mkBefore { first = "before"; })
];
# mkForce on individual element values; non-forced entries are discarded.
# Discarded values use a mix of: plain value, mkDefault(abort ...), mkOverride 100 (abort ...).
# The abort variants verify laziness: peelProperties sees the wrapper without forcing the content.
attrListInt.forceElementValue = [
{ a = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ a = mkForce 42; }
{ a = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ b = 2; }
];
# mkForce on attrset format
attrList.forceAttrset = mkMerge [
[
{ discarded = "gone"; }
]
(mkForce {
x = mkOrder 200 "x-val";
y = mkOrder 100 "y-val";
})
];
# mkForce on repeated key: forced entries override non-forced
attrList.forceRepeatedKey = [
{ x = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ x = mkForce "wins"; }
{ x = mkForce "wins 2"; }
];
# mkForce on repeated key across mkMerge
attrList.forceRepeatedKeyMerge = mkMerge [
[
{ x = "unused: overridden by mkForce"; }
{ x = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
]
[
{ x = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
]
[
{ x = mkForce "forced"; }
]
];
# mkForce on repeated key in attrset format across mkMerge
attrList.forceRepeatedKeyAttrs = mkMerge [
{
x = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated");
y = "kept";
}
{ x = mkForce "forced"; }
];
# mkForce only affects the key it's on, other keys survive
attrList.forcePartialAttrs = mkMerge [
{
x = "unused: overridden by mkForce";
y = "normal y";
}
{ x = mkForce "forced x"; }
];
# mkForce in attrset format overrides same key from list format
attrList.forceMixedFormats = mkMerge [
[
{ x = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ y = "list y"; }
]
{ x = mkForce "attrset forced x"; }
];
# Nesting: list format, mkOrder on element + mkForce on value
attrList.nestListOrderForce = mkMerge [
[
{ x = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
(mkOrder 500 { x = mkForce "forced-early"; })
(mkOrder 1500 { y = "late"; })
]
[
(mkOrder 100 { z = "earliest"; })
]
];
# Nesting: list format, mkOrder(mkForce(val)) on value
attrList.nestListOrderOfForce = mkMerge [
[
{ x = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ y = "plain-early"; }
]
[
{ x = mkOrder 1500 (mkForce "forced-late"); }
{ z = mkOrder 500 "earliest"; }
]
[
{ x = "unused: overridden by mkForce"; }
{ w = mkOrder 1200 "mid"; }
]
];
# Nesting: list format, mkForce(mkOrder(val)) on value
attrList.nestListForceOfOrder = mkMerge [
[
{ x = "unused: overridden by mkForce"; }
{ y = "plain-early"; }
]
[
{ x = mkForce (mkOrder 1500 "forced-late"); }
{ z = mkOrder 500 "earliest"; }
]
[
{ x = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated"); }
{ w = mkOrder 1200 "mid"; }
]
];
# Nesting: attrset format, mkOrder wrapping mkForce
attrList.nestAttrsOrderOfForce = mkMerge [
{
x = mkOverride 100 (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated");
y = "plain-early";
}
{
x = mkOrder 1500 (mkForce "forced-late");
z = mkOrder 500 "earliest";
}
{
x = "unused: overridden by mkForce";
w = mkOrder 1200 "mid";
}
];
# Nesting: attrset format, mkForce wrapping mkOrder
attrList.nestAttrsForceOfOrder = mkMerge [
{
x = "unused: overridden by mkForce";
y = "plain-early";
}
{
x = mkForce (mkOrder 1500 "forced-late");
z = mkOrder 500 "earliest";
}
{
x = mkDefault (abort "overridden by mkForce; laziness guarantee: MUST NOT be evaluated");
w = mkOrder 1200 "mid";
}
];
# mkIf false on individual element value filters it out (list format)
attrListInt.optionalValueList = [
{ a = mkIf true 1; }
{ b = mkIf false 2; }
{ c = 3; }
];
# mkIf false on individual element value filters it out (attrset format)
attrListInt.optionalValueAttrs = {
a = mkIf true 1;
b = mkIf false 2;
c = 3;
};
# submodule elemType: produces real valueMeta
attrListSubmodule = [
{
web = {
port = 80;
};
}
{
db = {
port = 5432;
host = "dbhost";
};
}
];
# asAttrs: unique keys — value is a plain attrset
asAttrs.unique = [
{ a = "alpha"; }
{ b = "beta"; }
];
# asAttrs: duplicate keys — last in order wins
asAttrs.duplicateKeys = mkMerge [
{ x = mkOrder 500 "first"; }
{ x = mkOrder 1500 "last"; }
{ y = "only"; }
];
# asAttrs: with ordering — value is attrset, ordered list in valueMeta
asAttrs.ordered = {
z = mkOrder 200 "z-val";
a = mkOrder 100 "a-val";
};
# asAttrs: with mkForce — forced key overrides
asAttrs.withForce = mkMerge [
{ x = "unused: overridden by mkForce"; }
{
x = mkForce "forced";
y = "kept";
}
];
# asAttrs: empty
asAttrs.empty = [ ];
# asAttrsDefault: unique keys
asAttrsDefault.unique = [
{ a = 1; }
{ b = 2; }
];
# asAttrsDefault: duplicate keys — default collects into lists
asAttrsDefault.duplicates = mkMerge [
{ x = mkOrder 500 10; }
{ x = mkOrder 1500 30; }
{ y = 99; }
[
{ x = 20; }
]
];
# either: attrList branch matches for list input
eitherAttrListOrInt = [
{ a = "hello"; }
{ b = "world"; }
];
# either: int input falls through to int branch
eitherAttrListOrIntFallback = 42;
# either (swapped): int first, attrList second — int input matches int
eitherIntOrAttrList = 42;
# either (swapped): list input falls through to attrList branch
eitherIntOrAttrListFallback = [
{ a = "hello"; }
];
# Bad: string where int expected
attrListInt.badValue = [
{ a = "not-an-int"; }
];
# Bad: list element with multiple keys
attrList.badListElem = [
{
a = "ok";
b = "extra";
}
];
# Bad: plain string instead of list or attrset
attrList.badString = "not-a-container";
# Bad: list element is a bare string, not a singleton attrset
attrList.badListString = [
"not a singleton attribute"
];
attrListStrict = builtins.mapAttrs (k: v: builtins.deepSeq v v) config.attrList;
attrListIntStrict = builtins.mapAttrs (k: v: builtins.deepSeq v v) config.attrListInt;
assertions =
let
c = lib.evalModules {
modules = [ ./declare-attrList.nix ];
};
cfg = c.config;
in
# List input preserves elements
assert
cfg.attrList.listInput == [
{ a = "alpha"; }
{ b = "beta"; }
];
# Attrset input with mkOrder: lower priority comes first
assert
cfg.attrList.attrsetOrdered == [
{ y = "y-val"; }
{ x = "x-val"; }
];
# Mixed input: mkOrder 50 < default 1000
assert
cfg.attrList.mixed == [
{ n = "from-attrset"; }
{ m = "from-list"; }
];
# Multiple definitions from separate modules concatenate
# (import module's definition comes before this module's)
assert
cfg.attrListInt.multiModule == [
{ b = 2; }
{ a = 1; }
];
# Attrset without mkOrder: all at default priority
assert builtins.length cfg.attrList.attrsetNoOrder == 2;
# Empty list stays empty
assert cfg.attrList.empty == [ ];
# List elements support mkOrder/mkBefore/mkAfter
assert
cfg.attrList.listOrdering == [
{ a = "before"; }
{ m = "default"; }
{ z = "after"; }
];
# Plain list entries are at default priority (1000)
assert
cfg.attrList.listDefaultPrio == [
{ before = "before"; }
{ mid = "list-entry"; }
{ after = "after"; }
];
# Ordering: 500 < 1000 (default) < 1500
assert
cfg.attrList.ordering == [
{ first = "first"; }
{ middle = "middle"; }
{ last = "last"; }
];
# mkBefore (500) < default (1000) < mkAfter (1500)
assert
cfg.attrList.beforeAfter == [
{ a = "before"; }
{ m = "default"; }
{ z = "after"; }
];
# mkIf true includes, mkIf false excludes
assert
cfg.attrList.withMkIf == [
{ yes = "included"; }
];
# mkOverride: only lowest priority override survives
assert
cfg.attrList.withOverride == [
{ winner = "wins"; }
];
# mkDefault is overridden by normal definitions
assert
cfg.attrList.withDefault == [
{ normal = "wins"; }
];
# mkForce discards other definitions
assert
cfg.attrList.withForce == [
{ forced = "wins"; }
];
# mkForce with mkOrder inside: ordering still works
assert
cfg.attrList.forceWithOrder == [
{ first = "before"; }
{ second = "after"; }
];
# mkForce on individual element values passes through
assert
cfg.attrListInt.forceElementValue == [
{ a = 42; }
{ b = 2; }
];
# mkForce on attrset format: discards other defs, ordering preserved
assert
cfg.attrList.forceAttrset == [
{ y = "y-val"; }
{ x = "x-val"; }
];
# mkForce on repeated key: forced entries override non-forced
assert
cfg.attrList.forceRepeatedKey == [
{ x = "wins"; }
{ x = "wins 2"; }
];
# mkForce on repeated key across mkMerge: forced wins
assert
cfg.attrList.forceRepeatedKeyMerge == [
{ x = "forced"; }
];
# mkForce on repeated key in attrset format: discards other x, keeps y
assert
cfg.attrList.forceRepeatedKeyAttrs == [
{ y = "kept"; }
{ x = "forced"; }
];
# mkForce only affects its own key
assert
cfg.attrList.forcePartialAttrs == [
{ y = "normal y"; }
{ x = "forced x"; }
];
# mkForce in attrset format overrides same key from list format
assert
cfg.attrList.forceMixedFormats == [
{ y = "list y"; }
{ x = "attrset forced x"; }
];
# Nesting: list format, mkOrder on element + mkForce on value
# z(100) < x-forced(500) < y(1500); x-discarded filtered by mkForce
assert
cfg.attrList.nestListOrderForce == [
{ z = "earliest"; }
{ x = "forced-early"; }
{ y = "late"; }
];
# Nesting: list format, mkOrder(mkForce(val)) on value
# z(500) < y(1000) < w(1200) < x-forced(1500); x-discarded entries filtered
assert
cfg.attrList.nestListOrderOfForce == [
{ z = "earliest"; }
{ y = "plain-early"; }
{ w = "mid"; }
{ x = "forced-late"; }
];
# Nesting: list format, mkForce(mkOrder(val)) on value
# z(500) < y(1000) < w(1200) < x-forced(1500); x-discarded entries filtered
assert
cfg.attrList.nestListForceOfOrder == [
{ z = "earliest"; }
{ y = "plain-early"; }
{ w = "mid"; }
{ x = "forced-late"; }
];
# Nesting: attrset format, mkOrder(mkForce(val))
# z(500) < y(1000) < w(1200) < x-forced(1500); x-discarded entries filtered
assert
cfg.attrList.nestAttrsOrderOfForce == [
{ z = "earliest"; }
{ y = "plain-early"; }
{ w = "mid"; }
{ x = "forced-late"; }
];
# Nesting: attrset format, mkForce(mkOrder(val))
# z(500) < y(1000) < w(1200) < x-forced(1500); x-discarded entries filtered
assert
cfg.attrList.nestAttrsForceOfOrder == [
{ z = "earliest"; }
{ y = "plain-early"; }
{ w = "mid"; }
{ x = "forced-late"; }
];
# mkIf false on individual element value filters it out (list format)
assert
cfg.attrListInt.optionalValueList == [
{ a = 1; }
{ c = 3; }
];
# mkIf false on individual element value filters it out (attrset format)
assert
cfg.attrListInt.optionalValueAttrs == [
{ a = 1; }
{ c = 3; }
];
# submodule: value, option descriptions, and valueMeta with real configuration metadata
assert
cfg.attrListSubmodule == [
{
web = {
host = "localhost";
port = 80;
};
}
{
db = {
host = "dbhost";
port = 5432;
};
}
];
assert
builtins.map (m: m.configuration.config) c.options.attrListSubmodule.valueMeta.attrList == [
{
host = "localhost";
port = 80;
}
{
host = "dbhost";
port = 5432;
}
];
assert
builtins.map (
m:
builtins.mapAttrs (n: o: o.description) (builtins.removeAttrs m.configuration.options [ "_module" ])
) c.options.attrListSubmodule.valueMeta.attrList == [
{
host = "Hostname";
port = "Port number";
}
{
host = "Hostname";
port = "Port number";
}
];
# valueMeta.attrList has one entry per (non-filtered) element
assert
c.options.attrList.valueMeta.attrs.listInput.attrList == [
{ }
{ }
];
assert
c.options.attrList.valueMeta.attrs.attrsetOrdered.attrList == [
{ }
{ }
];
assert
c.options.attrList.valueMeta.attrs.mixed.attrList == [
{ }
{ }
];
assert c.options.attrList.valueMeta.attrs.empty.attrList == [ ];
assert
c.options.attrListInt.valueMeta.attrs.optionalValueList.attrList == [
{ }
{ }
];
# either: headError is null for valid attrList input, so attrList branch is picked
assert
cfg.eitherAttrListOrInt == [
{ a = "hello"; }
{ b = "world"; }
];
# either: headError is non-null for int input, so int branch is picked
assert cfg.eitherAttrListOrIntFallback == 42;
# either (swapped): int first — int input matches
assert cfg.eitherIntOrAttrList == 42;
# either (swapped): list input falls through to attrList branch
assert
cfg.eitherIntOrAttrListFallback == [
{ a = "hello"; }
];
# asAttrs: unique keys — value is a plain attrset
assert
cfg.asAttrs.unique == {
a = "alpha";
b = "beta";
};
# ordered list preserved in valueMeta
assert
c.options.asAttrs.valueMeta.attrs.unique.attrListValue == [
{ a = "alpha"; }
{ b = "beta"; }
];
# asAttrs: duplicate keys — last in order wins
assert
cfg.asAttrs.duplicateKeys == {
x = "last";
y = "only";
};
assert
c.options.asAttrs.valueMeta.attrs.duplicateKeys.attrListValue == [
{ x = "first"; }
{ y = "only"; }
{ x = "last"; }
];
# asAttrs: ordered — value is attrset (unordered), list in valueMeta preserves order
assert
cfg.asAttrs.ordered == {
a = "a-val";
z = "z-val";
};
assert
c.options.asAttrs.valueMeta.attrs.ordered.attrListValue == [
{ a = "a-val"; }
{ z = "z-val"; }
];
# asAttrs: mkForce — forced key overrides, value is attrset
assert
cfg.asAttrs.withForce == {
x = "forced";
y = "kept";
};
# asAttrs: empty — value is empty attrset
assert cfg.asAttrs.empty == { };
# asAttrsDefault: unique keys — each value wrapped in singleton list
assert
cfg.asAttrsDefault.unique == {
a = [ 1 ];
b = [ 2 ];
};
# asAttrsDefault: duplicate keys — values collected into list in order
assert
cfg.asAttrsDefault.duplicates == {
x = [
10
20
30
];
y = [ 99 ];
};
assert
c.options.asAttrsDefault.valueMeta.attrs.duplicates.attrListValue == [
{ x = 10; }
{ y = 99; }
{ x = 20; }
{ x = 30; }
];
# valueMeta.definitions: mkDefinition records with mkOrder-wrapped single-key attrsets
# Use duplicateKeys which has mixed priorities and repeated keys
assert
let
defs = c.options.asAttrs.valueMeta.attrs.duplicateKeys.definitions;
extract = d: {
prio = d.value.priority;
value = d.value.content;
};
in
map extract defs == [
{
prio = 500;
value = {
x = "first";
};
}
{
prio = 1000;
value = {
y = "only";
};
}
{
prio = 1500;
value = {
x = "last";
};
}
];
# Round-trip: feed definitions through mapDefinitionValue + mkMerge into a listOf option
assert
let
rendered = lib.modules.mapDefinitionValue (attr: lib.cli.toCommandLineGNU { } attr) (
mkMerge c.options.asAttrs.valueMeta.attrs.duplicateKeys.definitions
);
result =
(lib.evalModules {
modules = [
{ options.out = mkOption { type = types.listOf types.str; }; }
{ config.out = rendered; }
# Interleave: mkOrder 800 lands between x(500) and y(1000)
{ config.out = mkOrder 800 [ "--interleaved" ]; }
];
}).config.out;
in
result == [
"-xfirst"
"--interleaved"
"-yonly"
"-xlast"
];
# Error cases are tested via checkConfigError in modules.sh
"ok";
};
}

View file

@ -167,6 +167,28 @@ in
elemType = str;
lazy = false;
}).description == "attribute set of string";
assert (attrListOf str).description == "attribute list of string";
assert (attrListOf int).description == "attribute list of signed integer";
assert (attrListOf bool).description == "attribute list of boolean";
assert (attrListOf (either int str)).description == "attribute list of (signed integer or string)";
assert (attrListOf (nullOr str)).description == "attribute list of (null or string)";
assert (attrListOf (listOf str)).description == "attribute list of list of string";
assert
(attrListOf (attrsOf int)).description == "attribute list of attribute set of signed integer";
assert (attrListOf (attrListOf str)).description == "attribute list of attribute list of string";
assert (attrListOf ints.positive).description == "attribute list of (positive integer, meaning >0)";
assert
(attrListOf (enum [
"a"
"b"
])).description == "attribute list of (one of \"a\", \"b\")";
assert
(attrListOf (strMatching "[0-9]+")).description
== "attribute list of string matching the pattern [0-9]+";
assert
(attrListOf (nonEmptyListOf str)).description == "attribute list of non-empty (list of string)";
assert (attrListOf (submodule { })).description == "attribute list of (submodule)";
assert (coercedTo str abort int).description == "signed integer or string convertible to it";
assert (coercedTo int abort str).description == "string or signed integer convertible to it";
assert (coercedTo bool abort str).description == "string or boolean convertible to it";

View file

@ -70,4 +70,16 @@ dir="$(nix-instantiate --eval --strict --read-write-mode --json --expr '(with im
EOF
) || die "cleanSourceWith + cleanSource"
dir="$(nix-instantiate --eval --strict --read-write-mode --json --expr '(with import <nixpkgs/lib>; "${
sources.sourceByGlobs '"$work"' [ "*.md" "**/*.o" ]
}")' | crudeUnquoteJSON)"
(cd "$dir"; find) | sort -f | diff -U10 - <(cat <<EOF
.
./module.o
./README.md
EOF
) || die "sourceByGlobs 1"
echo >&2 tests ok

View file

@ -7,14 +7,16 @@ let
functionArgs
pathExists
release
setFunctionArgs
toBaseDigits
version
versionSuffix
warn
;
inherit (lib)
foldr
fromJSON
isString
readFile
;
in
{
@ -502,7 +504,7 @@ in
On each release the first letter is bumped and a new animal is chosen
starting with that new letter.
*/
codeName = "Yarara";
codeName = "Zokor";
/**
Returns the current nixpkgs version suffix as string.
@ -789,7 +791,7 @@ in
importJSON :: Path -> Any
```
*/
importJSON = path: builtins.fromJSON (builtins.readFile path);
importJSON = path: fromJSON (readFile path);
/**
Reads a TOML file.
@ -836,7 +838,7 @@ in
importTOML :: Path -> Any
```
*/
importTOML = path: fromTOML (builtins.readFile path);
importTOML = path: fromTOML (readFile path);
/**
`warn` *`message`* *`value`*
@ -1046,7 +1048,7 @@ in
info = msg: builtins.trace "INFO: ${msg}";
showWarnings = warnings: res: lib.foldr (w: x: warn w x) res warnings;
showWarnings = warnings: res: foldr warn res warnings;
## Function annotations
@ -1177,7 +1179,10 @@ in
let
fArgs = functionArgs f;
in
g: setFunctionArgs g fArgs;
g: {
__functor = self: g;
__functionArgs = fArgs;
};
/**
Turns any non-callable values into constant functions.
@ -1325,11 +1330,11 @@ in
r = i - ((i / base) * base);
q = (i - r) / base;
in
[ r ] ++ go q;
go q ++ [ r ];
in
assert (isInt base);
assert (isInt i);
assert (base >= 2);
assert (i >= 0);
lib.reverseList (go i);
go i;
}

View file

@ -20,6 +20,7 @@ let
isStorePath
isString
substring
sort
throwIf
toDerivation
toList
@ -27,6 +28,7 @@ let
;
inherit (lib.lists)
concatLists
concatMap
elemAt
filter
foldl'
@ -70,6 +72,11 @@ let
mergeDefinitions
fixupOptionType
mergeOptionDecls
defaultOrderPriority
defaultOverridePriority
mkDefinition
mkOrder
mkOverride
;
inherit (lib.fileset)
isFileset
@ -805,6 +812,179 @@ rec {
substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
};
attrListOf = elemType: attrListWith { inherit elemType; };
attrListWith =
{
elemType,
asAttrs ? false,
mergeAttrValues ? _name: values: values,
}:
mkOptionType rec {
name = "attrListOf";
description = "attribute list of ${
optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
}";
descriptionClass = "composite";
check = {
__functor = _self: x: isList x || isAttrs x;
isV2MergeCoherent = true;
};
merge = {
__functor =
self: loc: defs:
(self.v2 { inherit loc defs; }).value;
v2 =
{ loc, defs }:
let
# Peel order and override properties from a value in any nesting order.
# Returns { value, prio, overridePrio }.
# mkOrder is stripped (we consume it for sorting).
# mkOverride is preserved in value (mergeDefinitions strips it).
peelProperties =
value:
let
type = value._type or null;
in
if type == "order" then
let
inner = peelProperties value.content;
in
{
inherit (inner) value overridePrio;
prio = value.priority;
}
else if type == "override" then
let
inner = peelProperties value.content;
in
{
inherit (inner) prio;
overridePrio = value.priority;
# Re-wrap mkOverride around the inner value (with mkOrder stripped)
value = mkOverride value.priority inner.value;
}
else
{
inherit value;
prio = defaultOrderPriority;
overridePrio = defaultOverridePriority;
};
# Extract { file, key, value, prio, overridePrio } from a single-key attrset,
# optionally wrapped in mkOrder at the element level (list format).
extractItem =
file: raw:
let
hasOrder = isType "order" raw;
item = if hasOrder then raw.content else raw;
key = head (attrNames item);
peeled = peelProperties item.${key};
in
if isAttrs item && length (attrNames item) == 1 then
peeled
// {
inherit file key;
prio = if hasOrder then raw.priority else peeled.prio;
}
else
throw "A definition for option `${showOption loc}' is not of type `${description}'. ${
if !isAttrs item then
"Each list element must be an attribute set, but got ${builtins.typeOf item}"
else
"Each list element must be a single-key attribute set, but got ${toString (length (attrNames item))} keys"
}.${
showDefs [
{
inherit file;
value = raw;
}
]
}";
# Convert a definition to a flat list of { file, key, value, prio, overridePrio }
defToItems =
def:
if isList def.value then
map (extractItem def.file) def.value
else
# isAttrs: properties are on the values directly
map (
key:
peelProperties def.value.${key}
// {
inherit (def) file;
inherit key;
}
) (attrNames def.value);
allItems = concatMap defToItems defs;
# Per key, find the highest override priority (lowest number)
winningOverridePrio = foldl' (
acc: item:
let
prev = acc.${item.key} or defaultOverridePriority;
in
if item.overridePrio < prev then
acc // { ${item.key} = item.overridePrio; }
else
# minimize `//` operations
acc
) { } allItems;
# Keep only items at the winning override priority for their key
items = sort (a: b: a.prio < b.prio) (
filter (
item: item.overridePrio == winningOverridePrio.${item.key} or defaultOverridePriority
) allItems
);
evals = filter (e: e.eval.optionalValue ? value) (
map (item: {
inherit (item) key file prio;
eval = mergeDefinitions (loc ++ [ item.key ]) elemType [
{
inherit (item) file value;
}
];
}) items
);
attrListValue = map (e: { ${e.key} = e.eval.optionalValue.value or e.eval.mergedValue; }) evals;
in
{
headError = checkDefsForError check loc defs;
value = if asAttrs then zipAttrsWith mergeAttrValues attrListValue else attrListValue;
valueMeta.attrList = map (e: e.eval.checkedAndMerged.valueMeta) evals;
/**
The ordered list representation, especially useful when asAttrs is set.
*/
valueMeta.attrListValue = attrListValue;
valueMeta.definitions = map (
e:
mkDefinition {
inherit (e) file;
value = mkOrder e.prio { ${e.key} = e.eval.optionalValue.value or e.eval.mergedValue; };
}
) evals;
};
};
emptyValue = {
value = if asAttrs then { } else [ ];
};
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]);
getSubModules = elemType.getSubModules;
substSubModules =
m:
attrListWith {
inherit asAttrs mergeAttrValues;
elemType = elemType.substSubModules m;
};
typeMerge = t: null; # Disable type merging
nestedTypes.elemType = elemType;
};
attrsOf = elemType: attrsWith { inherit elemType; };
# A version of attrsOf that's lazy in its values at the expense of

View file

@ -862,6 +862,13 @@
githubId = 209175;
name = "Alesya Huzik";
};
Ai-Ya-Ya = {
email = "spg2500@gmail.com";
github = "Ai-Ya-Ya";
githubId = 72513839;
matrix = "aiya:catgirl.cloud";
name = "aiya";
};
aij = {
email = "aij+git@mrph.org";
github = "aij";
@ -4275,6 +4282,12 @@
{ fingerprint = "D088 A5AF C45B 78D1 CD4F 457C 6957 B3B6 46F2 BB4E"; }
];
};
c6rg0 = {
email = "c6rg0@protonmail.com";
github = "c6rg0";
githubId = 64259221;
name = "c6rg0";
};
caarlos0 = {
name = "Carlos A Becker";
email = "carlos@becker.software";
@ -4448,12 +4461,6 @@
githubId = 289492;
name = "Philip Horger";
};
candeira = {
email = "javier@candeira.com";
github = "candeira";
githubId = 91694;
name = "Javier Candeira";
};
caniko = {
email = "gpg@rotas.mozmail.com";
github = "caniko";
@ -4845,11 +4852,6 @@
githubId = 18648043;
name = "Daniel Cartwright";
};
chewblacka = {
github = "chewblacka";
githubId = 18430320;
name = "John Garcia";
};
Chili-Man = {
email = "dr.elhombrechile@gmail.com";
name = "Diego Rodriguez";
@ -5062,12 +5064,6 @@
github = "cigrainger";
githubId = 3984794;
};
ciil = {
email = "simon@lackerbauer.com";
github = "ciil";
githubId = 3956062;
name = "Simon Lackerbauer";
};
cilki = {
github = "cilki";
githubId = 10459406;
@ -6326,12 +6322,6 @@
githubId = 4347318;
email = "davinci42.cn@gmail.com";
};
davisrichard437 = {
email = "davisrichard437@gmail.com";
github = "davisrichard437";
githubId = 85075437;
name = "Richard Davis";
};
davorb = {
email = "davor@davor.se";
github = "davorb";
@ -7370,12 +7360,6 @@
github = "DSeeLP";
githubId = 46624152;
};
dsferruzza = {
email = "david.sferruzza@gmail.com";
github = "dsferruzza";
githubId = 1931963;
name = "David Sferruzza";
};
dsluijk = {
name = "Dany Sluijk";
email = "nix@dany.dev";
@ -7626,6 +7610,11 @@
githubId = 54573;
name = "Edward Amsden";
};
eana = {
github = "eana";
githubId = 18534280;
name = "Jonas Eana";
};
earldouglas = {
email = "james@earldouglas.com";
github = "earldouglas";
@ -8674,6 +8663,13 @@
githubId = 88741530;
name = "Fabian Rigoll";
};
fabiob = {
email = "fabio@atelie.dev.br";
github = "fabiob";
githubId = 140875;
name = "Fábio Batista";
keys = [ { fingerprint = "D2D8 69D8 5EEC 30AD D327 B4A5 6CD5 5257 DB01 8B72"; } ];
};
fallenbagel = {
name = "fallenbagel";
github = "fallenbagel";
@ -10146,6 +10142,12 @@
githubId = 6893840;
name = "Yacine Hmito";
};
gquetel = {
email = "gregor.quetel@telecom-paris.fr";
github = "gquetel";
githubId = 48437427;
name = "Grégor Quetel";
};
gracicot = {
email = "dev@gracicot.com";
matrix = "@gracicot-59e8f173d73408ce4f7ac803:gitter.im";
@ -15605,12 +15607,6 @@
githubId = 4312404;
name = "Chris Rendle-Short";
};
lightdiscord = {
email = "root@arnaud.sh";
github = "lightdiscord";
githubId = 24509182;
name = "Arnaud Pascal";
};
lightquantum = {
email = "self@lightquantum.me";
github = "PhotonQuantum";
@ -15817,12 +15813,6 @@
githubId = 23727619;
name = "Luca Ruperto";
};
lnl7 = {
email = "daiderd@gmail.com";
github = "LnL7";
githubId = 689294;
name = "Daiderd Jordan";
};
lo1tuma = {
email = "schreck.mathias@gmail.com";
github = "lo1tuma";
@ -16075,6 +16065,12 @@
githubId = 83420438;
name = "Lewis";
};
luc65r = {
email = "lucas@ransan.fr";
github = "luc65r";
githubId = 59375051;
name = "Lucas Ransan";
};
LucaGuerra = {
email = "luca@guerra.sh";
github = "LucaGuerra";
@ -17397,12 +17393,6 @@
githubId = 613740;
name = "Martin Baillie";
};
mbbx6spp = {
email = "me@susanpotter.net";
github = "mbbx6spp";
githubId = 564;
name = "Susan Potter";
};
mbe = {
email = "brandonedens@gmail.com";
github = "brandonedens";
@ -19254,6 +19244,12 @@
name = "Naufal Fikri";
keys = [ { fingerprint = "1575 D651 E31EC 6117A CF0AA C1A3B 8BBC A515 8835"; } ];
};
naurissteins = {
name = "Nauris Steins";
email = "me@naurissteins.com";
github = "naurissteins";
githubId = 5653746;
};
naxdy = {
name = "Naxdy";
email = "naxdy@naxdy.org";
@ -19549,6 +19545,11 @@
githubId = 10180857;
name = "Anmol Sethi";
};
nhshah15 = {
github = "nhshah15";
githubId = 31825306;
name = "Neil Shah";
};
ni5arga = {
email = "hello@ni5arga.com";
github = "ni5arga";
@ -26592,6 +26593,13 @@
github = "supermarin";
githubId = 627285;
};
Superredstone = {
email = "patrickcanal3@gmail.com";
matrix = "@superredstone:matrix.org";
github = "Superredstone";
githubId = 56410215;
name = "Patrick Canal";
};
SuperSandro2000 = {
email = "sandro.jaeckel@gmail.com";
matrix = "@sandro:supersandro.de";
@ -26688,12 +26696,6 @@
githubId = 120188;
name = "Scott W. Dunlop";
};
sweber = {
email = "sweber2342+nixpkgs@gmail.com";
github = "sweber83";
githubId = 19905904;
name = "Simon Weber";
};
sweenu = {
name = "sweenu";
email = "contact@sweenu.xyz";
@ -28115,12 +28117,6 @@
githubId = 50843046;
name = "tornax";
};
toschmidt = {
email = "tobias.schmidt@in.tum.de";
github = "toschmidt";
githubId = 27586264;
name = "Tobias Schmidt";
};
totalchaos = {
email = "basil.keeler@outlook.com";
github = "totalchaos05";
@ -28532,6 +28528,12 @@
githubId = 30677291;
name = "u2x1";
};
u3kkasha = {
email = "fida.waseque@gmail.com";
github = "u3kkasha";
githubId = 146055002;
name = "Fida Waseque Choudhury";
};
uakci = {
name = "uakci";
email = "git@uakci.space";
@ -29376,6 +29378,14 @@
github = "wamserma";
githubId = 60148;
};
wanderer = {
name = "Martin Becze";
email = "martin@becze.org";
matrix = "@null_radix:matrix.org";
github = "wanderer";
githubId = 158211;
keys = [ { fingerprint = "82CB 91F4 F3E4 04A5 DAB2 78E8 8B99 1293 8FF2 1020"; } ];
};
wariuccio = {
name = "Wariuccio";
github = "wariuccio";
@ -30482,6 +30492,12 @@
githubId = 7040031;
name = "Yannik Sander";
};
ysnt-yes = {
name = "Yes";
github = "ysnt-yes";
githubId = 85800291;
email = "yes@ysnt.live";
};
yuannan = {
email = "brandon@emergence.ltd";
github = "yuannan";

View file

@ -6,7 +6,7 @@
and builds all derivations (or at least a reasonable subset) affected by
these overrides.
By default, it checks `configuration-{common,nix,ghc-9.8.x}.nix`. You can
By default, it checks `configuration-{common,nix,ghc-9.10.x}.nix`. You can
invoke it like this:
nix-build maintainers/scripts/haskell/test-configurations.nix --keep-going
@ -55,6 +55,8 @@
nixpkgsPath ? ../../..,
config ? {
allowBroken = true;
# TODO(@sternenseemann): better way to workaround throw-ing aliases?
allowAliases = false;
},
skipEvalErrors ? true,
skipBinaryGHCs ? true,

View file

@ -104,6 +104,7 @@ sed -r \
-e '/ hledger-lib /d' \
-e '/ hledger-ui /d' \
-e '/ hledger-web /d' \
-e '/ spacecookie /d' \
< "${tmpfile_new}" >> $stackage_config
# Explanations:
# cabal2nix, distribution-nixpkgs, jailbreak-cabal, language-nix: These are our packages and we know what we are doing.
@ -111,9 +112,4 @@ sed -r \
# ShellCheck: latest version of command-line dev tool.
# Agda: The Agda community is fast-moving; we strive to always include the newest versions of Agda and the Agda packages in nixpkgs.
# Work around Stackage LTS including a bogus version of cassava which has been deprecated on Hackage.
# See <https://github.com/haskell-hvr/cassava/issues/248>.
# TODO(@sternenseemann): drop this once the situation has been resolved in Stackage LTS
sed -e 's/cassava ==0.5.5.0/cassava >= 0.5.4.0 && (> 0.5.5.0 || < 0.5.5.0) && < 0.6.0.0/' -i "$stackage_config"
echo "$old_version -> $version"

View file

@ -49,6 +49,7 @@ lpeglabel,,,,1.6.0,,
lrexlib-gnu,,,,,,
lrexlib-oniguruma,,,,,,junestepp
lrexlib-pcre,,,,,,
lrexlib-pcre2,,,,,,wishstudio
lrexlib-posix,,,,,,
lsp-progress.nvim,,,,,5.1,gepbird
lsqlite3,,,,,,
@ -167,6 +168,7 @@ telescope.nvim,,,,,5.1,
tiktoken_core,,,,,,natsukium
tl,,,,,,mephistophiles
toml-edit,,,,,5.1,mrcjkb
tomlua,,,,,,birdee
tree-sitter-cli,,,,,,
tree-sitter-http,,,,0.0.33-1,,
tree-sitter-norg,,,,,5.1,mrcjkb

1 name rockspec ref server version luaversion maintainers
49 lrexlib-gnu
50 lrexlib-oniguruma junestepp
51 lrexlib-pcre
52 lrexlib-pcre2 wishstudio
53 lrexlib-posix
54 lsp-progress.nvim 5.1 gepbird
55 lsqlite3
168 tiktoken_core natsukium
169 tl mephistophiles
170 toml-edit 5.1 mrcjkb
171 tomlua birdee
172 tree-sitter-cli
173 tree-sitter-http 0.0.33-1
174 tree-sitter-norg 5.1 mrcjkb

View file

@ -494,6 +494,47 @@ Composed types are types that take a type as parameter. `listOf
Displays the option as `foo.<id>` in the manual.
`types.attrListOf` *`t`*
: An ordered list of single-attribute attribute sets, where each value is of *`t`* type.
The output is always `[ { name1 = value1; } { name2 = value2; } ... ]`.
Definitions can be provided in two formats, which may be mixed via `lib.mkMerge`, `imports`, etc:
- **List format**: `[ { a = 1; } { b = 2; } ]` — each element must be a single-attribute attribute set.
Elements may be wrapped in `lib.mkOrder` (or `lib.mkBefore`/`lib.mkAfter`) to control ordering;
unwrapped elements use the default order priority.
- **Attribute set format**: `{ a = lib.mkOrder 100 1; b = 2; }` — each name-value pair becomes a single-attribute attribute set in the output.
Values may be wrapped in `lib.mkOrder` (or `lib.mkBefore`/`lib.mkAfter`) to control ordering.
Values without `lib.mkOrder` use the default priority.
Multiple definitions of the same option are concatenated and then sorted by priority.
Entries at the same priority level preserve their definition order.
`types.attrListWith` { *`elemType`*, *`asAttrs`* ? false, *`mergeAttrValues`* ? _name: values: values }
: An ordered list of single-attribute attribute sets, where each value is of *`elemType`* type.
**Parameters**
`elemType` (Required)
: Specifies the type of each value in the attribute list.
`asAttrs`
: When `true`, the option value is an attribute set instead of a list.
Duplicate keys are merged using `mergeAttrValues`.
The ordered list is always available via `valueMeta.attrListValue`.
`mergeAttrValues`
: A function `name: values: mergedValue` that controls how duplicate keys
are combined when `asAttrs = true`. This is passed as the callback to
`lib.zipAttrsWith`. The `values` list is in order of priority.
By default, all values are collected into a list.
**Behavior**
- `attrListWith { elemType = t; }` is equivalent to `attrListOf t`
`types.uniq` *`t`*

View file

@ -2363,6 +2363,21 @@
"ch-release-notes": [
"release-notes.html#ch-release-notes"
],
"sec-release-26.11": [
"release-notes.html#sec-release-26.11"
],
"sec-release-26.11-highlights": [
"release-notes.html#sec-release-26.11-highlights"
],
"sec-release-26.11-new-modules": [
"release-notes.html#sec-release-26.11-new-modules"
],
"sec-release-26.11-incompatibilities": [
"release-notes.html#sec-release-26.11-incompatibilities"
],
"sec-release-26.11-notable-changes": [
"release-notes.html#sec-release-26.11-notable-changes"
],
"sec-release-26.05": [
"release-notes.html#sec-release-26.05"
],

View file

@ -3,6 +3,7 @@
This section lists the release notes for each stable version of NixOS and current unstable revision.
```{=include=} sections
rl-2611.section.md
rl-2605.section.md
rl-2511.section.md
rl-2505.section.md

View file

@ -23,11 +23,11 @@
# Pinned Nixpkgs archive
#
# Use `curl -I https://channels.nixos.org/nixos-26.05` to get the
# latest commit of the stable channel and `nix-prefetch-url --unpack`
# to compute its sha256 hash.
# latest commit of the stable channel and `nix --extra-experimental-features nix-command store prefetch-file --unpack`
# to compute its hash.
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/c217913993d6.tar.gz";
sha256 = "026mprs324330pfazlgbw987qmsa8ligglarvqbcxzig2kgw0lqg";
hash = "sha256-D1PA3xQv/s4W3lnR9yJFSld8UOLr0a/cBWMQMXS+1Qg=";
};
in
import "${nixpkgs}/nixos" {
@ -132,6 +132,8 @@
- [Howdy](https://github.com/boltgolt/howdy), a Windows Hello™ style facial authentication program for Linux.
- [SuiteNumérique Drive](https://github.com/suitenumerique/drive), a collaborative file sharing and document management platform that scales. Built with Django and React. Open source alternative to Sharepoint or Google Drive.
- [linux-enable-ir-emitter](https://github.com/EmixamPP/linux-enable-ir-emitter), a tool used to set up IR cameras, used with Howdy.
- [udp-over-tcp](https://github.com/mullvad/udp-over-tcp), a tunnel for proxying UDP traffic over a TCP stream. Available as `services.udp-over-tcp`.
@ -191,6 +193,17 @@
- `services.pyload` has been removed because the package it relies on does not exist anymore in nixpkgs due to vulnerabilities and being unmaintained.
- Removed the ability from systemd to start units installed via `nix-env -i`.
This feature is hardly used and most units do not work without NixOS specific
changes anyways. Removing this allows us to drop a custom systemd patch
improving systemd maintainability and update speed.
- Removed the ability from systemd to depend on open-yet-unformatted dm-crypt
devices as systemd device units. For example you cannot call systemd-makefs
on such devices. This was never implemented upstream but patched into
Nixpkgs's systemd. Removing this allows us to drop a custom systemd patch
improving systemd maintainability and update speed.
- `linux_hardened` kernel has been removed due to a lack of maintenance.
- `services.tandoor-recipes` now uses a sub-directory for media files by default starting with `26.05`. Existing setups should move media files out of the data directory and adjust `services.tandoor-recipes.extraConfig.MEDIA_ROOT` accordingly. See [Migrating media files for pre 26.05 installations](#module-services-tandoor-recipes-migrating-media).
@ -207,9 +220,22 @@
- `services.mattermost` now defaults to version 11, which has dropped support for MySQL in favor of Postgres. As a result, all support for MySQL has been removed from the module.
See the [migration steps](https://docs.mattermost.com/deployment-guide/manual-postgres-migration.html) if you were not running Postgres.
Note that version 11 also restricts the user limit to 250 [by default](https://forum.mattermost.com/t/clarification-request-on-user-limits-max-250-user-server-v-11/25309);
see the `pkgs.mattermost` removeUserLimit and removeFreeBadge options combined with `services.mattermost.package` to change this behavior. For example:
```nix
{
services.mattermost.package = pkgs.mattermost.override {
removeUserLimit = true;
removeFreeBadge = true;
};
}
```
- `post-resume.target` has been removed. See {manpage}`systemd.special(7)` about `sleep.target` for instructions on ordering a process after resume with `ExecStop=`.
- `services.vsftpd` no longer automatically configures a PAM module. This means configurations using `services.vsftpd.localUsers` will no longer work unless `services.vsftpd.enableVirtualUsers` and `services.vsftpd.userDbPath` are also configured. The old behaviour can be restored by setting `security.pam.services.vsftpd.enable = true`, although this only ever worked by accident and may not be secure.
- `services.kubernetes.addons.dns.coredns` has been renamed to `services.kubernetes.addons.dns.corednsImage` and now expects a
package instead of attrs. Now, by default, nixpkgs.coredns in conjunction with dockerTools.buildImage is used, instead
of pulling the upstream container image from Docker Hub. If you want the old behavior, you can set:
@ -373,6 +399,10 @@ See <https://github.com/NixOS/nixpkgs/issues/481673>.
- `services.esphome` no longer uses `DynamicUser`. The service now runs as a static `esphome` system user. systemd handles the migration from `/var/lib/private/esphome` automatically, but users with [impermanence](https://github.com/nix-community/impermanence) setups should ensure `/var/lib/esphome` is persisted.
- `programs.pqos-wrapper` module has been deleted as the corresponding package has been dropped from nixpkgs.
- `programs.benchexec.enable = true` no longer sets `programs.pqos-wrapper.enable = true` as the corresponding module has been deleted.
## Other Notable Changes {#sec-release-26.05-notable-changes}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

View file

@ -1,4 +1,4 @@
# Release 26.11 (2026.11/??) {#sec-release-26.11}
# Release 26.11 ("Zokor", 2026.11/??) {#sec-release-26.11}
## Highlights {#sec-release-26.11-highlights}
@ -16,12 +16,7 @@
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- `programs.pqos-wrapper` module has been deleted
as the corresponding package has been dropped from nixpkgs.
- `programs.benchexec.enable = true`
no longer sets `programs.pqos-wrapper.enable = true`
as the corresponding module has been deleted.
- Create the first release note entry in this section!
## Other Notable Changes {#sec-release-26.11-notable-changes}

View file

@ -1475,7 +1475,6 @@ class NspawnMachine(BaseMachine):
self.start_command = start_command
self.process = None
self.pid = None
self.machine_sock_path = self.tmp_dir / f"{self.name}-nspawn.sock"
@ -1486,14 +1485,13 @@ class NspawnMachine(BaseMachine):
return f'ssh -o User=root -o ProxyCommand="{proxy_cmd}" bash'
def release(self) -> None:
if self.pid is None:
if self.process is None:
return
if self.machine_sock:
self.machine_sock.close()
self.logger.info(f"kill NspawnMachine (pid {self.pid})")
assert self.process is not None
self.logger.info(f"kill NspawnMachine (pid {self.process.pid})")
self.process.terminate()
# Wait for the wrapper to finish its context-manager cleanups
# (veth/bridge/netns teardown) before returning, so the driver's
@ -1502,7 +1500,7 @@ class NspawnMachine(BaseMachine):
self.process.wait(timeout=30)
except subprocess.TimeoutExpired:
self.logger.error(
f"NspawnMachine {self.name} (pid {self.pid}) did not exit after SIGTERM; sending SIGKILL"
f"NspawnMachine {self.name} (pid {self.process.pid}) did not exit after SIGTERM; sending SIGKILL"
)
self.process.kill()
self.process.wait()
@ -1694,9 +1692,7 @@ class NspawnMachine(BaseMachine):
stdout=subprocess.PIPE,
)
self.pid = self.process.pid
self.log(f"systemd-nspawn running (pid {self.pid})")
self.log(f"systemd-nspawn running (pid {self.process.pid})")
journal_thread = threading.Thread(target=self._stream_journal, daemon=True)
journal_thread.start()

View file

@ -150,7 +150,14 @@ in
config.enableDebugHook -> isLinux
) "The debugging hook is not supported for macOS host systems!";
{
name = "vm-test-run-${config.name}";
name =
let
inherit (config.driverConfiguration) containers vms;
kind = lib.concatStringsSep "-and-" (
(lib.optional (containers != { }) "container") ++ (lib.optional (vms != { }) "vm")
);
in
"${kind}-test-run-${config.name}";
requiredSystemFeatures = lib.attrNames (lib.filterAttrs (_: v: v) config.requiredFeatures);

View file

@ -6,6 +6,7 @@
}:
let
cfg = config.console;
i18nCfg = config.i18n;
makeColor = i: lib.concatMapStringsSep "," (x: "0x" + lib.substring (2 * i) 2 x);
@ -164,8 +165,10 @@ in
environment.systemPackages = [ pkgs.kbd ];
# Let systemd-vconsole-setup.service do the work of setting up the
# virtual consoles.
environment.etc."vconsole.conf".source = vconsoleConf true;
# virtual consoles. Skip when imperative so localectl can manage it.
environment.etc."vconsole.conf" = lib.mkIf (!i18nCfg.imperativeLocale) {
source = vconsoleConf true;
};
# Provide kbd with additional packages.
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
@ -211,13 +214,22 @@ in
"systemd-vconsole-setup.service"
];
# When imperative, seed /etc/vconsole.conf on first boot from declared
# defaults so the keymap isn't lost before localectl is ever used
systemd.tmpfiles.rules = lib.mkIf i18nCfg.imperativeLocale [
"C /etc/vconsole.conf - - - - ${vconsoleConf true}"
];
systemd.services.reload-systemd-vconsole-setup = {
description = "Reset console on configuration changes";
wantedBy = [ "multi-user.target" ];
restartTriggers = [
(config.environment.etc."vconsole.conf".source)
(consoleEnv pkgs.kbd)
];
restartTriggers =
lib.optionals (!i18nCfg.imperativeLocale) [
(config.environment.etc."vconsole.conf".source)
]
++ [
(consoleEnv pkgs.kbd)
];
reloadIfChanged = true;
serviceConfig = {
RemainAfterExit = true;

View file

@ -5,24 +5,29 @@
...
}:
let
cfg = config.i18n;
localeConf = pkgs.writeText "locale.conf" ''
LANG=${cfg.defaultLocale}
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n}=${v}") cfg.extraLocaleSettings)}
'';
sanitizeUTF8Capitalization =
lang: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] lang);
aggregatedLocales =
lib.optionals (config.i18n.defaultLocale != "C") [
"${config.i18n.defaultLocale}/${config.i18n.defaultCharset}"
lib.optionals (cfg.defaultLocale != "C") [
"${cfg.defaultLocale}/${cfg.defaultCharset}"
]
++ lib.pipe config.i18n.extraLocaleSettings [
++ lib.pipe cfg.extraLocaleSettings [
# See description of extraLocaleSettings for why is this ignored here.
(x: lib.removeAttrs x [ "LANGUAGE" ])
(lib.mapAttrs (n: v: (sanitizeUTF8Capitalization v)))
# C locales are always installed
(lib.filterAttrs (n: v: v != "C"))
(lib.mapAttrsToList (LCRole: lang: lang + "/" + (config.i18n.localeCharsets.${LCRole} or "UTF-8")))
(lib.mapAttrsToList (LCRole: lang: lang + "/" + (cfg.localeCharsets.${LCRole} or "UTF-8")))
]
++ (map sanitizeUTF8Capitalization (
lib.optionals (builtins.isList config.i18n.extraLocales) config.i18n.extraLocales
lib.optionals (builtins.isList cfg.extraLocales) cfg.extraLocales
))
++ (lib.optional (builtins.isString config.i18n.extraLocales) config.i18n.extraLocales);
++ (lib.optional (builtins.isString cfg.extraLocales) cfg.extraLocales);
in
{
###### interface
@ -35,8 +40,8 @@ in
default =
if pkgs.glibcLocales != null then
pkgs.glibcLocales.override {
allLocales = lib.elem "all" config.i18n.supportedLocales;
locales = config.i18n.supportedLocales;
allLocales = lib.elem "all" cfg.supportedLocales;
locales = cfg.supportedLocales;
}
else
null;
@ -150,6 +155,16 @@ in
'';
};
imperativeLocale = lib.mkEnableOption ''
imperative locale and keyboard management via localectl.
When enabled, locale and keyboard settings can be changed at runtime
using `localectl set-locale` and `localectl set-keymap`.
When disabled (the default), these settings are managed declaratively
through {option}`i18n.defaultLocale`, {option}`i18n.extraLocaleSettings`,
and {option}`console.keyMap`.
'';
};
};
@ -161,8 +176,8 @@ in
lib.optional
(
!(
(lib.subtractLists config.i18n.supportedLocales aggregatedLocales) == [ ]
|| lib.elem "all" config.i18n.supportedLocales
(lib.subtractLists cfg.supportedLocales aggregatedLocales) == [ ]
|| lib.elem "all" cfg.supportedLocales
)
)
''
@ -178,29 +193,36 @@ in
environment.systemPackages =
# We increase the priority a little, so that plain glibc in systemPackages can't win.
lib.optional (config.i18n.glibcLocales != null && config.i18n.supportedLocales != [ ]) (
lib.setPrio (-1) config.i18n.glibcLocales
lib.optional (cfg.glibcLocales != null && cfg.supportedLocales != [ ]) (
lib.setPrio (-1) cfg.glibcLocales
);
environment.sessionVariables = {
LANG = config.i18n.defaultLocale;
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
}
// config.i18n.extraLocaleSettings;
# When imperative, leave LANG/LC_* to pam_systemd so /etc/set-environment
# does not override what localectl wrote to /etc/locale.conf.
// lib.optionalAttrs (!cfg.imperativeLocale) (
{
LANG = cfg.defaultLocale;
}
// cfg.extraLocaleSettings
);
systemd.globalEnvironment =
lib.mkIf (config.i18n.glibcLocales != null && config.i18n.supportedLocales != [ ])
{
LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
};
systemd.globalEnvironment = lib.mkIf (cfg.glibcLocales != null && cfg.supportedLocales != [ ]) {
LOCALE_ARCHIVE = "${cfg.glibcLocales}/lib/locale/locale-archive";
};
# /etc/locale.conf is used by systemd.
environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
LANG=${config.i18n.defaultLocale}
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
)}
'';
# If imperative, see below
environment.etc."locale.conf" = lib.mkIf (!cfg.imperativeLocale) {
source = localeConf;
};
# When imperative, seed /etc/locale.conf on first boot from declared defaults
# so the system doesnt fall back to C.UTF-8
systemd.tmpfiles.rules = lib.mkIf cfg.imperativeLocale [
"C /etc/locale.conf - - - - ${localeConf}"
];
};
}

View file

@ -86,10 +86,6 @@ in
# This way services are restarted when tzdata changes.
systemd.globalEnvironment.TZDIR = tzdir;
systemd.services.systemd-timedated.environment = lib.optionalAttrs (config.time.timeZone != null) {
NIXOS_STATIC_TIMEZONE = "1";
};
environment.etc = {
zoneinfo.source = tzdir;
}

View file

@ -11,6 +11,23 @@ let
cfg = config.environment;
suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (
envVar: suffixes:
lib.flip lib.concatMap cfg.profiles (profile: map (suffix: "${profile}${suffix}") suffixes)
);
combinedSessionVars = lib.zipAttrsWith (n: lib.concatLists) [
# Make sure security wrappers are prioritized without polluting
# shell environments with an extra entry. Sessions which depend on
# pam for its environment will otherwise have eg. broken sudo. In
# particular Gnome Shell sometimes fails to source a proper
# environment from a shell.
{ PATH = [ config.security.wrapperDir ]; }
(lib.mapAttrs (n: lib.toList) cfg.sessionVariables)
suffixedVariables
];
in
{
@ -73,13 +90,11 @@ in
};
config = {
environment.etc."environment.d/50-systemd-path.conf".text = ''
PATH="${lib.concatStringsSep ":" combinedSessionVars.PATH}"
'';
environment.etc."pam/environment".text =
let
suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (
envVar: suffixes:
lib.flip lib.concatMap cfg.profiles (profile: map (suffix: "${profile}${suffix}") suffixes)
);
# We're trying to use the same syntax for PAM variables and env variables.
# That means we need to map the env variables that people might use to their
# equivalent PAM variable.
@ -88,21 +103,7 @@ in
pamVariable =
n: v: ''${n} DEFAULT="${lib.concatStringsSep ":" (map replaceEnvVars (lib.toList v))}"'';
pamVariables = lib.concatStringsSep "\n" (
lib.mapAttrsToList pamVariable (
lib.zipAttrsWith (n: lib.concatLists) [
# Make sure security wrappers are prioritized without polluting
# shell environments with an extra entry. Sessions which depend on
# pam for its environment will otherwise have eg. broken sudo. In
# particular Gnome Shell sometimes fails to source a proper
# environment from a shell.
{ PATH = [ config.security.wrapperDir ]; }
(lib.mapAttrs (n: lib.toList) cfg.sessionVariables)
suffixedVariables
]
)
);
pamVariables = lib.concatStringsSep "\n" (lib.mapAttrsToList pamVariable combinedSessionVars);
in
''
${pamVariables}

View file

@ -50,7 +50,6 @@
rxvt-unicode-unwrapped
rxvt-unicode-unwrapped-emoji
st
termite
tmux
wezterm
yaft

View file

@ -505,7 +505,7 @@ EOF
# This should work for single and multi-device systems.
# still needs subvolume support
if ($fsType eq "bcachefs") {
my ($status, @info) = runCommand("bcachefs fs usage $rootDir$mountPoint");
my ($status, @info) = runCommand("@bcachefs@ fs usage $rootDir$mountPoint");
my $UUID = $info[0];
if ($status == 0 && $UUID =~ /^Filesystem:[ \t\n]*([0-9a-z-]+)/) {

View file

@ -30,15 +30,20 @@ let
name = "nixos-generate-config";
src = ./nixos-generate-config.pl;
replacements = {
perl = "${
perl = lib.getExe (
pkgs.perl.withPackages (p: [
p.FileSlurp
p.ConfigIniFiles
])
}/bin/perl";
);
hostPlatformSystem = pkgs.stdenv.hostPlatform.system;
detectvirt = "${config.systemd.package}/bin/systemd-detect-virt";
btrfs = "${pkgs.btrfs-progs}/bin/btrfs";
detectvirt = lib.getExe' config.systemd.package "systemd-detect-virt";
bcachefs =
if pkgs.bcachefs-tools.meta.broken then
lib.getExe' pkgs.coreutils "false"
else
lib.getExe pkgs.bcachefs-tools;
btrfs = lib.getExe pkgs.btrfs-progs;
inherit (config.system.nixos-generate-config) configuration desktopConfiguration flake;
xserverEnabled = config.services.xserver.enable;
};

View file

@ -1697,6 +1697,7 @@
./services/web-apps/komga.nix
./services/web-apps/lanraragi.nix
./services/web-apps/lasuite-docs.nix
./services/web-apps/lasuite-drive.nix
./services/web-apps/lasuite-meet.nix
./services/web-apps/lauti.nix
./services/web-apps/lemmy.nix

View file

@ -33,7 +33,10 @@ in
enable = lib.mkOption {
type = lib.types.bool;
default = false;
default = builtins.pathExists config.programs.command-not-found.dbPath;
defaultText = lib.literalExpression ''
builtins.pathExists config.programs.command-not-found.dbPath
'';
description = ''
Whether interactive shells should show which Nix package (if
any) provides a missing command.
@ -45,6 +48,11 @@ in
};
dbPath = lib.mkOption {
type = lib.types.path;
default = pkgs.path + "/programs.sqlite";
defaultText = lib.literalExpression ''
pkgs.path + "/programs.sqlite"
'';
description = ''
Absolute path to `programs.sqlite`, which contains mappings from binary names to package names.
@ -54,39 +62,29 @@ in
`/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite`.
If you do so, you can update it with `sudo nix-channels --update`.
'';
type = lib.types.path;
};
};
config = lib.mkMerge [
{
programs.command-not-found = {
enable = lib.mkDefault (builtins.pathExists cfg.dbPath);
dbPath = pkgs.path + "/programs.sqlite";
};
}
config = lib.mkIf cfg.enable {
programs.bash.interactiveShellInit = ''
command_not_found_handle() {
'${commandNotFound}/bin/command-not-found' "$@"
}
'';
(lib.mkIf cfg.enable {
programs.bash.interactiveShellInit = ''
command_not_found_handle() {
'${commandNotFound}/bin/command-not-found' "$@"
}
'';
programs.zsh.interactiveShellInit = ''
command_not_found_handler() {
'${commandNotFound}/bin/command-not-found' "$@"
}
'';
programs.zsh.interactiveShellInit = ''
command_not_found_handler() {
'${commandNotFound}/bin/command-not-found' "$@"
}
'';
# NOTE: Fish by itself checks for nixos command-not-found, let's instead makes it explicit.
programs.fish.interactiveShellInit = ''
function fish_command_not_found
"${commandNotFound}/bin/command-not-found" $argv
end
'';
# NOTE: Fish by itself checks for nixos command-not-found, let's instead makes it explicit.
programs.fish.interactiveShellInit = ''
function fish_command_not_found
"${commandNotFound}/bin/command-not-found" $argv
end
'';
environment.systemPackages = [ commandNotFound ];
})
];
environment.systemPackages = [ commandNotFound ];
};
}

View file

@ -15,6 +15,8 @@ in
programs.gamemode = {
enable = lib.mkEnableOption "GameMode to optimise system performance on demand";
package = lib.mkPackageOption pkgs "gamemode" { };
enableRenice =
lib.mkEnableOption "CAP_SYS_NICE on gamemoded to support lowering process niceness"
// {
@ -53,7 +55,7 @@ in
config = lib.mkIf cfg.enable {
environment = {
systemPackages = [ pkgs.gamemode ];
systemPackages = [ cfg.package ];
etc."gamemode.ini".source = configFile;
};
@ -63,14 +65,14 @@ in
gamemoded = {
owner = "root";
group = "root";
source = "${pkgs.gamemode}/bin/gamemoded";
source = "${cfg.package}/bin/gamemoded";
capabilities = "cap_sys_nice+ep";
};
};
};
systemd = {
packages = [ pkgs.gamemode ];
packages = [ cfg.package ];
user.services.gamemoded = {
# Use pkexec from the security wrappers to allow users to
# run libexec/cpugovctl & libexec/gpuclockctl as root with

View file

@ -47,6 +47,9 @@ in
{
config = lib.mkIf (cfg.enable && nixPackage.pname == "lix") {
# Require the tun kernel module for pasta, can be disabled if pasta is not used.
boot.kernelModules.tun = lib.mkDefault true;
environment.systemPackages = [
nixPackage
pkgs.nix-info

View file

@ -116,7 +116,7 @@ in
# config file. starship appears to use a hardcoded config location
# rather than one inside an XDG folder:
# https://github.com/starship/starship/blob/686bda1706e5b409129e6694639477a0f8a3f01b/src/configure.rs#L651
if [[ ! -f "$${STARSHIP_CONFIG:-$HOME/.config/starship.toml}" ]]; then
if [[ ! -f "''${STARSHIP_CONFIG:-$HOME/.config/starship.toml}" ]]; then
export STARSHIP_CONFIG=${settingsFile}
fi
eval "$(${cfg.package}/bin/starship init bash --print-full-init)"
@ -154,7 +154,7 @@ in
# config file. starship appears to use a hardcoded config location
# rather than one inside an XDG folder:
# https://github.com/starship/starship/blob/686bda1706e5b409129e6694639477a0f8a3f01b/src/configure.rs#L651
if [[ ! -f "$${STARSHIP_CONFIG:-$HOME/.config/starship.toml}" ]]; then
if [[ ! -f "''${STARSHIP_CONFIG:-$HOME/.config/starship.toml}" ]]; then
export STARSHIP_CONFIG=${settingsFile}
fi
eval "$(${cfg.package}/bin/starship init zsh)"

View file

@ -23,7 +23,7 @@ in
enable = lib.mkEnableOption "TUN mode of Throne";
setuid = lib.mkEnableOption ''
setting suid bit for throne-core to run as root, which is less
setting suid bit for ThroneCore to run as root, which is less
secure than default setcap method but closer to upstream assumptions.
Enable this if you find the default setcap method configured in
this module doesn't work for you
@ -36,8 +36,8 @@ in
environment.systemPackages = [ cfg.package ];
security.wrappers.throne-core = lib.mkIf cfg.tunMode.enable {
source = "${cfg.package}/share/throne/Core";
security.wrappers."ThroneCore" = lib.mkIf cfg.tunMode.enable {
source = "${cfg.package}/share/throne/ThroneCore";
owner = "root";
group = "root";
setuid = lib.mkIf cfg.tunMode.setuid true;
@ -49,7 +49,7 @@ in
# avoid resolvectl password prompt popping up three times
# https://github.com/SagerNet/sing-tun/blob/0686f8c4f210f4e7039c352d42d762252f9d9cf5/tun_linux.go#L1062
# We use a hack here to determine whether the requested process is throne-core
# We use a hack here to determine whether the requested process is ThroneCore
# Detect whether its capabilities contain at least `net_admin` and `net_raw`.
# This does not reduce security, as we can already bypass `resolved` with them.
# Alternatives to consider:
@ -61,7 +61,7 @@ in
# change its own cmdline. `/proc/<pid>/exe` is reliable but kernel forbids
# checking that entry of process from different users, and polkit runs `spawn`
# as an unprivileged user.
# 3. Put throne-core into a systemd service, and let polkit check service name.
# 3. Put ThroneCore into a systemd service, and let polkit check service name.
# This is the most secure and convenient way but requires heavy modification
# to Throne source code. Would be good to let upstream support that eventually.
security.polkit.extraConfig =
@ -69,6 +69,7 @@ in
''
polkit.addRule(function(action, subject) {
const allowedActionIds = [
"org.freedesktop.resolve1.revert",
"org.freedesktop.resolve1.set-domains",
"org.freedesktop.resolve1.set-default-route",
"org.freedesktop.resolve1.set-dns-servers"

View file

@ -81,7 +81,12 @@ in
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
environment.systemPackages = [ cfg.package ];
environment = {
systemPackages = [ cfg.package ];
# Allows lua stub file to be accessed from /run/current-system/sw/share/hypr
pathsToLink = [ "/share/hypr" ];
};
# Hyprland needs permissions to give itself SCHED_RR on startup:
# https://github.com/hyprwm/Hyprland/blob/main/src/init/initHelpers.cpp

View file

@ -51,7 +51,7 @@ in
with pkgs; [ swaylock foot dmenu ];
'';
example = lib.literalExpression ''
with pkgs; [ termite rofi light ]
with pkgs; [ alacritty rofi light ]
'';
description = ''
Extra packages to be installed system wide. See

View file

@ -116,7 +116,7 @@ in
with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ];
'';
example = lib.literalExpression ''
with pkgs; [ i3status i3status-rust termite rofi light ]
with pkgs; [ i3status i3status-rust alacritty rofi light ]
'';
description = ''
Extra packages to be installed system wide. See

View file

@ -249,8 +249,8 @@ in
setopt ${builtins.concatStringsSep " " cfg.setOptions}
''}
# Alternative method of determining short and full hostname.
HOST=${config.networking.fqdnOrHostName}
# Determine current fqdn hostname
HOST=$(hostname --fqdn)
# Setup command line history.
# Don't export these, otherwise other shells (bash) will try to use same HISTFILE.

View file

@ -155,7 +155,8 @@ in
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = !useYTMusic;
# breaks pyopenssl's cffi calls, used in remote access feature
MemoryDenyWriteExecute = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;

View file

@ -90,12 +90,9 @@ in
Plugins = {
Enabled = mkOption {
default = (builtins.length cfg.plugins) != 0;
defaultText = literalExpression "builtins.length \"\${config.services.navidrome.plugins != 0}\"";
default = true;
description = ''
Enable plugin support in navidrome.
This is automatically enabled if {option}`services.navidrome.plugins` is used.
'';
};
Folder = mkOption {

View file

@ -208,7 +208,7 @@ in
LoadCredential = config.systemd.services.radicle-node.serviceConfig.LoadCredential or [ ];
BindReadOnlyPaths = config.systemd.services.radicle-node.serviceConfig.BindReadOnlyPaths ++ [
"/run/credentials/radicle-ci-broker.service/xyz.radicle.node.secret:/var/lib/radicle/keys/radicle"
"/run/credentials/radicle-ci-broker.service/dev.radicle.node.secret:/var/lib/radicle/keys/radicle"
];
ReadWritePaths = [ RAD_HOME ];

View file

@ -469,6 +469,7 @@ in
pkgs.gnome-maps
pkgs.gnome-music
pkgs.gnome-system-monitor
pkgs.gnome-tecla
pkgs.gnome-weather
pkgs.loupe
pkgs.nautilus

View file

@ -40,7 +40,6 @@ in
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
seatd
sdnotify-wrapper
];
users.groups.seat = lib.mkIf (cfg.group == "seat") { };
@ -55,7 +54,7 @@ in
Type = "notify";
NotifyAccess = "all";
SyslogIdentifier = "seatd";
ExecStart = "${pkgs.sdnotify-wrapper}/bin/sdnotify-wrapper ${pkgs.seatd.bin}/bin/seatd -n 1 -u ${cfg.user} -g ${cfg.group} -l ${cfg.logLevel}";
ExecStart = "${lib.getExe' pkgs.s6 "s6-notify-socket-from-fd"} ${pkgs.seatd.bin}/bin/seatd -n 1 -u ${cfg.user} -g ${cfg.group} -l ${cfg.logLevel}";
RestartSec = 1;
Restart = "always";
};

View file

@ -26,6 +26,7 @@ in
config = lib.mkIf cfg.enable {
environment.systemPackages = [
pkgs.cosmic-comp
pkgs.cosmic-icons
cfg.package
];

View file

@ -44,6 +44,20 @@ let
defaultSessionName = config.services.displayManager.defaultSession;
setSessionScript = pkgs.callPackage ../x11/display-managers/account-service-util.nix { };
greeterUsers = lib.genAttrs' [ null 1 2 3 4 ] (
i:
let
# adding 1 to create `gdm-greeter{-2,-3,-4,-5}`
suffix = lib.optionalString (i != null) "-${toString (i + 1)}";
in
lib.nameValuePair "gdm-greeter${suffix}" {
isSystemUser = true;
uid = 60578 + (if i == null then 0 else i);
group = "gdm";
home = "/run/gdm/home/gdm-greeter${suffix}";
}
);
in
{
@ -103,6 +117,12 @@ in
[ "services" "xserver" "displayManager" "gdm" "autoLogin" "delay" ]
[ "services" "displayManager" "gdm" "autoLogin" "delay" ]
)
(lib.mkRemovedOptionModule [
"services"
"displayManager"
"gdm"
"wayland"
] "Disabling this option is no longer supported with GNOME 50.")
];
meta = {
@ -128,14 +148,6 @@ in
'';
};
wayland = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Allow GDM to run on Wayland instead of Xserver.
'';
};
autoSuspend = lib.mkOption {
default = true;
description = ''
@ -188,6 +200,8 @@ in
config = lib.mkIf cfg.enable {
warnings = lib.optional config.services.pulseaudio.enable "Support for Pulseaudio + gdm will be removed in NixOS 26.11";
services.xserver.displayManager.lightdm.enable = false;
users.users = lib.mkMerge [
@ -198,24 +212,8 @@ in
group = "gdm";
description = "GDM user";
};
gdm-greeter = {
isSystemUser = true;
uid = 60578;
group = "gdm";
home = "/run/gdm";
};
}
(lib.genAttrs' [ 1 2 3 4 ] (
i:
lib.nameValuePair "gdm-greeter-${toString i}" {
isSystemUser = true;
uid = 60578 + i;
group = "gdm";
home = "/run/gdm-${toString i}";
}
))
greeterUsers
];
users.groups.gdm.gid = config.ids.gids.gdm;
@ -257,17 +255,20 @@ in
};
};
systemd.tmpfiles.rules = [
"d /run/gdm/.config 0711 gdm gdm"
]
++ lib.optionals config.services.pulseaudio.enable [
"d /run/gdm/.config/pulse 0711 gdm gdm"
"L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}"
]
++ lib.optionals config.services.gnome.gnome-initial-setup.enable [
# Create stamp file for gnome-initial-setup to prevent it starting in GDM.
"f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes"
];
systemd.tmpfiles.rules =
lib.optionals config.services.pulseaudio.enable (
lib.concatLists (
lib.mapAttrsToList (name: user: [
"d ${user.home}/.config 0711 ${name} gdm"
"d ${user.home}/.config/pulse 0711 ${name} gdm"
"L+ ${user.home}/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}"
]) greeterUsers
)
)
++ lib.optionals config.services.gnome.gnome-initial-setup.enable [
# Create stamp file for gnome-initial-setup to prevent it starting in GDM.
"f /run/gdm/gdm.ran-initial-setup 0711 gdm gdm - yes"
];
# Otherwise GDM will not be able to start correctly and display Wayland sessions
systemd.packages = [
@ -361,7 +362,6 @@ in
# presented and there's a little delay.
services.displayManager.gdm.settings = {
daemon = lib.mkMerge [
{ WaylandEnable = cfg.wayland; }
# nested if else didn't work
(lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.autoLogin.delay != 0) {
TimedLoginEnable = true;

View file

@ -113,12 +113,12 @@ in
{
name = "nologin";
control = "requisite";
modulePath = "pam_nologin.so";
modulePath = "${config.security.pam.package}/lib/security/pam_nologin.so";
}
{
name = "ly-normal-user";
control = "required";
modulePath = "pam_succeed_if.so";
modulePath = "${config.security.pam.package}/lib/security/pam_succeed_if.so";
settings.quiet = true;
args = lib.mkBefore [
"uid"
@ -129,7 +129,7 @@ in
{
name = "permit";
control = "required";
modulePath = "pam_permit.so";
modulePath = "${config.security.pam.package}/lib/security/pam_permit.so";
}
];

View file

@ -125,12 +125,12 @@ in
{
name = "nologin";
control = "requisite";
modulePath = "pam_nologin.so";
modulePath = "${config.security.pam.package}/lib/security/pam_nologin.so";
}
{
name = "permit";
control = "required";
modulePath = "pam_permit.so";
modulePath = "${config.security.pam.package}/lib/security/pam_permit.so";
}
];
@ -166,7 +166,7 @@ in
# Load environment from /etc/environment and ~/.pam_environment
name = "env";
control = "required";
modulePath = "pam_env.so";
modulePath = "${config.security.pam.package}/lib/security/pam_env.so";
settings.conffile = "/etc/pam/environment";
settings.readenv = 0;
}
@ -174,7 +174,7 @@ in
# Always let the greeter start without authentication
name = "permit";
control = "required";
modulePath = "pam_permit.so";
modulePath = "${config.security.pam.package}/lib/security/pam_permit.so";
}
];
@ -183,7 +183,7 @@ in
# No action required for account management
name = "permit";
control = "required";
modulePath = "pam_permit.so";
modulePath = "${config.security.pam.package}/lib/security/pam_permit.so";
}
];
@ -192,7 +192,7 @@ in
# Can't change password
name = "deny";
control = "required";
modulePath = "pam_deny.so";
modulePath = "${config.security.pam.package}/lib/security/pam_deny.so";
}
];
@ -201,7 +201,7 @@ in
# Setup session
name = "unix";
control = "required";
modulePath = "pam_unix.so";
modulePath = "${config.security.pam.package}/lib/security/pam_unix.so";
}
{
name = "systemd";

View file

@ -12,10 +12,7 @@ let
in
{
meta.maintainers = with lib.maintainers; [
sweber
hexa
];
meta.maintainers = with lib.maintainers; [ hexa ];
imports = [
(lib.mkRemovedOptionModule [

View file

@ -17,8 +17,8 @@ let
};
credentials = {
privateKey = "xyz.radicle.node.secret";
privateKeyPassphrase = "xyz.radicle.node.passphrase";
privateKey = "dev.radicle.node.secret";
privateKeyPassphrase = "dev.radicle.node.passphrase";
};
# Convenient wrapper to run `rad` in the namespaces of `radicle-node.service`

View file

@ -155,8 +155,12 @@ in
wideArea = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable wide-area service discovery.";
default = false;
description = ''
Whether to enable wide-area service discovery.
It is recommended to keep this options disabled as it exposes the system to `CVE-2024-52615`/`GHSA-x6vp-f33h-h32g`.
'';
};
reflector = lib.mkOption {
@ -279,6 +283,10 @@ in
};
config = lib.mkIf cfg.enable {
warnings = [
(lib.mkIf cfg.wideArea "Enabling `services.avahi.wideArea` exposes this system to `CVE-2024-52615`.")
];
users.users.avahi = {
description = "avahi-daemon privilege separation user";
home = "/var/empty";

View file

@ -591,7 +591,7 @@ in
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = optionals (!config.services.resolved.enable) [ pkgs.openresolv ];
path = [ pkgs.shadow ] ++ optionals (!config.services.resolved.enable) [ pkgs.openresolv ];
serviceConfig = {
ExecStart = "${getExe client.wrapper} service run";

View file

@ -19,6 +19,7 @@ let
in
''
# Can't use -u (unset) because api.sh uses API_URL before it is set
set +u
set -eo pipefail
pihole="${lib.getExe pihole}"
jq="${lib.getExe pkgs.jq}"
@ -29,21 +30,24 @@ in
if [ ! -f '${cfg.settings.files.gravity}' ]; then
$pihole -g
# Send SIGRTMIN to FTL, which makes it reload the database, opening the newly created one
${lib.getExe' pkgs.procps "kill"} -s SIGRTMIN $(systemctl show --property MainPID --value ${config.systemd.services.pihole-ftl.name})
${lib.getExe' pkgs.procps "kill"} -s SIGRTMIN "$(systemctl show --property MainPID --value ${config.systemd.services.pihole-ftl.name})"
fi
# shellcheck disable=SC1091
source ${pihole}/share/pihole/advanced/Scripts/api.sh
# shellcheck disable=SC1091
source ${pihole}/share/pihole/advanced/Scripts/utils.sh
any_failed=0
addList() {
local payload="$1"
local payload="$1" result type error
echo "Adding list: $payload"
local result=$(PostFTLData "lists" "$payload")
type=$($jq -r '.type' <<< "$payload")
result=$(PostFTLData "lists?type=$type" "$payload")
local error="$($jq '.error' <<< "$result")"
error="$($jq '.error' <<< "$result")"
if [[ "$error" != "null" ]]; then
echo "Error: $error"
any_failed=1
@ -61,7 +65,7 @@ in
echo "Added list ID $id: $result"
}
for i in 1 2 3; do
for _ in 1 2 3; do
(TestAPIAvailability) && break
echo "Retrying API shortly..."
${lib.getExe' pkgs.coreutils "sleep"} .5s

View file

@ -491,11 +491,18 @@ in
users.groups.${cfg.group} = { };
environment.etc."pihole/pihole.toml" = {
source = settingsFile;
user = cfg.user;
group = cfg.group;
mode = "400";
environment.etc = {
"pihole/pihole.toml" = {
source = settingsFile;
user = cfg.user;
group = cfg.group;
mode = "400";
};
"pihole/versions".text = ''
CORE_VERSION=${cfg.piholePackage.src.src.tag}
FTL_VERSION=${cfg.package.src.tag}
'';
};
environment.systemPackages = [ cfg.pihole ];

View file

@ -158,6 +158,7 @@ let
builtins.storeDir
"/etc/"
]
++ cfg.extraConfigFiles
++ lib.optional (cfg.secretsFile != null) cfg.secretsFile;
DeviceAllow = "/dev/rfkill rw";
LockPersonality = true;

View file

@ -0,0 +1,542 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib)
concatMapStringsSep
concatStringsSep
escapeShellArg
getExe
hasSuffix
mapAttrs
match
mkEnableOption
mkIf
mkPackageOption
mkOption
types
optional
optionalString
;
cfg = config.services.lasuite-drive;
pythonEnvironment = mapAttrs (
_: value:
if value == null then
"None"
else if value == true then
"True"
else if value == false then
"False"
else
toString value
) cfg.settings;
commonServiceConfig = {
RuntimeDirectory = "lasuite-drive";
StateDirectory = "lasuite-drive";
WorkingDirectory = "/var/lib/lasuite-drive";
User = "lasuite-drive";
DynamicUser = true;
SupplementaryGroups = mkIf cfg.redis.createLocally [
config.services.redis.servers.lasuite-drive.group
];
# hardening
AmbientCapabilities = "";
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
MemoryDenyWriteExecute = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
UMask = "0077";
EnvironmentFile = cfg.environmentFiles;
};
proxySuffix = if match "unix:.*" cfg.bind != null then ":" else "";
# Convert environment variables to be used as systemd-run arguments
envArgs = lib.concatStringsSep " " (
lib.mapAttrsToList (name: value: "-E ${escapeShellArg "${name}=${value}"}") pythonEnvironment
);
# Easier usage of django manage.py stuff
manage = pkgs.writeShellScriptBin "lasuite-drive-manage" ''
exec ${lib.getExe' config.systemd.package "systemd-run"} \
-p User=${commonServiceConfig.User} \
-p DynamicUser=yes \
-p StateDirectory=${commonServiceConfig.StateDirectory} \
${optionalString cfg.redis.createLocally "-p SupplementaryGroups=${config.services.redis.servers.lasuite-drive.group} \\"}
${concatMapStringsSep "\n" (envFile: "-p EnvironmentFile=${envFile} \\") cfg.environmentFiles}
--working-directory=${commonServiceConfig.WorkingDirectory} \
--quiet --collect --pipe --pty \
${envArgs} ${lib.getExe cfg.package} "$@"
'';
in
{
options.services.lasuite-drive = {
enable = mkEnableOption "SuiteNumérique Drive";
package = mkPackageOption pkgs "lasuite-drive" { };
bind = mkOption {
type = types.str;
default = "unix:/run/lasuite-drive/gunicorn.sock";
example = "127.0.0.1:8000";
description = ''
The path, host/port or file descriptior to bind the gunicorn socket to.
See <https://docs.gunicorn.org/en/stable/settings.html#bind> for possible options.
'';
};
enableNginx = mkEnableOption "enable and configure Nginx for reverse proxying" // {
default = true;
};
secretKeyPath = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to the Django secret key.
The key can be generated using:
```
python3 -c 'import secrets; print(secrets.token_hex())'
```
:::{.note}
If not specified, a secret key is automatically generated and stored in the state directory.
:::
'';
};
s3Url = mkOption {
type = types.str;
description = ''
URL of the S3 bucket.
'';
};
postgresql = {
createLocally = mkOption {
type = types.bool;
default = false;
description = ''
Configure local PostgreSQL database server for drive.
'';
};
};
redis = {
createLocally = mkOption {
type = types.bool;
default = false;
description = ''
Configure local Redis cache server for drive.
'';
};
};
gunicorn = {
extraArgs = mkOption {
type = types.listOf types.str;
default = [
"--name=drive"
"--workers=3"
];
description = ''
Extra arguments to pass to the gunicorn process.
'';
};
};
celery = {
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Extra arguments to pass to the celery process.
'';
};
};
domain = mkOption {
type = types.str;
description = ''
Domain name of the drive instance.
'';
};
settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf (
types.nullOr (
types.oneOf [
types.str
types.bool
types.path
types.int
]
)
);
options = {
DJANGO_CONFIGURATION = mkOption {
type = types.str;
internal = true;
default = "Production";
description = "The configuration that Django will use";
};
DJANGO_SETTINGS_MODULE = mkOption {
type = types.str;
internal = true;
default = "drive.settings";
description = "The configuration module that Django will use";
};
DJANGO_SECRET_KEY_FILE = mkOption {
type = types.path;
default =
if cfg.secretKeyPath == null then "/var/lib/lasuite-drive/django_secret_key" else cfg.secretKeyPath;
description = "The path to the file containing Django's secret key";
};
DATA_DIR = mkOption {
type = types.path;
default = "/var/lib/lasuite-drive";
description = "Path to the data directory";
readOnly = true;
};
DJANGO_ALLOWED_HOSTS = mkOption {
type = types.listOf types.str;
default =
if cfg.enableNginx then
[
"localhost"
"127.0.0.1"
cfg.domain
]
else
[ ];
defaultText = lib.literalExpression ''
if cfg.enableNginx then [ "localhost" "127.0.0.1" cfg.domain ] else [ ]
'';
apply = list: concatStringsSep "," list;
description = "Comma-separated list of hosts that are able to connect to the server";
};
DB_NAME = mkOption {
type = types.str;
default = "lasuite-drive";
description = "Name of the database";
};
DB_USER = mkOption {
type = types.str;
default = "lasuite-drive";
description = "User of the database";
};
DB_HOST = mkOption {
type = types.nullOr types.str;
default = if cfg.postgresql.createLocally then "/run/postgresql" else null;
description = "Host of the database";
};
REDIS_URL = mkOption {
type = types.nullOr types.str;
default =
if cfg.redis.createLocally then
"unix://${config.services.redis.servers.lasuite-drive.unixSocket}?db=1"
else
null;
description = "URL of the redis backend";
};
CELERY_BROKER_URL = mkOption {
type = types.nullOr types.str;
default =
if cfg.redis.createLocally then
"redis+socket://${config.services.redis.servers.lasuite-drive.unixSocket}?db=2"
else
null;
description = "URL of the redis backend for celery";
};
};
};
default = { };
example = ''
{
AWS_S3_ENDPOINT_URL = "https://s3.us-west.amazonaws.com";
}
'';
description = ''
Configuration options of drive.
See <https://github.com/suitenumerique/drive/blob/v${cfg.package.version}/docs/env.md>
`REDIS_URL` and `CELERY_BROKER_URL` are set if `services.lasuite-drive.redis.createLocally` is true.
`DB_HOST` is set if `services.lasuite-drive.postgresql.createLocally` is true.
'';
};
environmentFiles = mkOption {
type = types.listOf types.path;
default = [ ];
description = ''
Path to environment files.
This can be useful to pass secrets to drive via tools like `agenix` or `sops`.
'';
};
};
config = mkIf cfg.enable {
warnings = mkIf (cfg.enableNginx && !(hasSuffix "/" cfg.s3Url)) [
''
services.lasuite-drive.s3Url should end with a trailing slash (/).
This could break the HTTP requests by nginx to the S3 backend.
''
];
environment.systemPackages = [ manage ];
systemd.services.lasuite-drive = {
description = "Drive from SuiteNumérique";
after = [
"network-online.target"
]
++ (optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wants =
(optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wantedBy = [ "multi-user.target" ];
preStart = ''
if [ ! -f .version ]; then
touch .version
fi
${optionalString (cfg.secretKeyPath == null) ''
if [[ ! -f /var/lib/lasuite-drive/django_secret_key ]]; then
(
umask 0377
tr -dc A-Za-z0-9 < /dev/urandom | head -c64 | ${pkgs.moreutils}/bin/sponge /var/lib/lasuite-drive/django_secret_key
)
fi
''}
if [ "${cfg.package.version}" != "$(cat .version)" ]; then
${getExe cfg.package} migrate
echo -n "${cfg.package.version}" > .version
fi
'';
environment = pythonEnvironment;
serviceConfig = {
BindReadOnlyPaths = "${cfg.package}/share/static:/var/lib/lasuite-drive/static";
ExecStart = utils.escapeSystemdExecArgs (
[
(lib.getExe' cfg.package "gunicorn")
"--bind=${cfg.bind}"
]
++ cfg.gunicorn.extraArgs
++ [ "drive.wsgi:application" ]
);
}
// commonServiceConfig;
};
systemd.services.lasuite-drive-celery = {
description = "Docs Celery broker from SuiteNumérique";
after = [
"network-online.target"
]
++ (optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wants =
(optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wantedBy = [ "multi-user.target" ];
environment = pythonEnvironment;
serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs (
[ (lib.getExe' cfg.package "celery") ]
++ cfg.celery.extraArgs
++ [
"--app=drive.celery_app"
"worker"
]
);
}
// commonServiceConfig;
};
systemd.services.lasuite-drive-beat = {
description = "Docs Celery beat from SuiteNumérique";
after = [
"network-online.target"
]
++ (optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wants =
(optional cfg.postgresql.createLocally "postgresql.service")
++ (optional cfg.redis.createLocally "redis-lasuite-drive.service");
wantedBy = [ "multi-user.target" ];
environment = pythonEnvironment;
serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs (
[ (lib.getExe' cfg.package "celery") ]
++ cfg.celery.extraArgs
++ [
"--app=drive.celery_app"
"beat"
]
);
}
// commonServiceConfig;
};
services.postgresql = mkIf cfg.postgresql.createLocally {
enable = true;
ensureDatabases = [ "lasuite-drive" ];
ensureUsers = [
{
name = "lasuite-drive";
ensureDBOwnership = true;
}
];
};
services.redis.servers.lasuite-drive = mkIf cfg.redis.createLocally { enable = true; };
services.nginx = mkIf cfg.enableNginx {
enable = true;
virtualHosts.${cfg.domain} = {
extraConfig = ''
error_page 401 /401.html;
error_page 403 /403.html;
error_page 404 /404.html;
'';
root = cfg.package.frontend;
locations."/" = {
tryFiles = "$uri $uri.html index.html $uri/ =404";
};
locations."~ '^/explorer/items/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$'" =
{
tryFiles = "$uri /explorer/items/[id].html";
};
locations."~ '^/explorer/items/files/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$'" =
{
tryFiles = "$uri /explorer/items/files/[id].html";
};
locations."~ '^/wopi/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$'" = {
tryFiles = "$uri /wopi/[id].html";
};
locations."/static/" = {
alias = "${cfg.package}/share/static/";
};
locations."/api" = {
proxyPass = "http://${cfg.bind}";
recommendedProxySettings = true;
};
locations."/admin" = {
proxyPass = "http://${cfg.bind}";
recommendedProxySettings = true;
};
locations."/media-auth" = {
proxyPass = "http://${cfg.bind}${proxySuffix}/api/v1.0/items/media-auth/";
recommendedProxySettings = true;
extraConfig = ''
proxy_set_header X-Original-URL $request_uri;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-Method $request_method;
'';
};
locations."/media/" = {
proxyPass = cfg.s3Url;
extraConfig = ''
auth_request /media-auth;
auth_request_set $authHeader $upstream_http_authorization;
auth_request_set $authDate $upstream_http_x_amz_date;
auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256;
proxy_set_header Authorization $authHeader;
proxy_set_header X-Amz-Date $authDate;
proxy_set_header X-Amz-Content-SHA256 $authContentSha256;
add_header Content-Disposition "attachment";
'';
};
locations."/media/preview/" = {
proxyPass = cfg.s3Url;
extraConfig = ''
auth_request /media-auth;
auth_request_set $authHeader $upstream_http_authorization;
auth_request_set $authDate $upstream_http_x_amz_date;
auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256;
proxy_set_header Authorization $authHeader;
proxy_set_header X-Amz-Date $authDate;
proxy_set_header X-Amz-Content-SHA256 $authContentSha256;
'';
};
};
};
};
meta = {
buildDocsInSandbox = false;
maintainers = [ lib.maintainers.soyouzpanda ];
};
}

View file

@ -145,9 +145,11 @@ let
pkgs.writeShellScriptBin "mastodon-tootctl" ''
set -a
export RAILS_ROOT="${cfg.package}"
export HOME="/var/lib/mastodon"
source "${envFile}"
source /var/lib/mastodon/.secrets_env
${sourceExtraEnv}
cd /var/lib/mastodon
sudo=exec
if [[ "$USER" != ${cfg.user} ]]; then

View file

@ -149,7 +149,10 @@ in
systemd.services.miniflux = {
description = "Miniflux service";
wantedBy = [ "multi-user.target" ];
requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service";
requires = lib.optionals cfg.createDatabaseLocally [
"miniflux-dbsetup.service"
"postgresql.target"
];
after = [
"network.target"
]

View file

@ -68,6 +68,11 @@ It requires elevated permissions to become the `nextcloud` user. Given the way t
escalation is implemented, parameters passed via the environment to Nextcloud are
currently ignored, except for `OC_PASS` and `NC_PASS`.
::: {.warning}
When Polkit is enabled, the command being executed by `nextcloud-occ` might be logged
into the system's journal. Be careful to not leak secrets that way!
:::
Custom service units that need to run `nextcloud-occ` either need elevated privileges
or the systemd configuration from `nextcloud-setup.service` (recommended):

View file

@ -89,6 +89,8 @@ in
};
config = lib.mkIf cfg.enable {
environment.etc."pihole/versions".text = "WEB_VERSION=${cfg.package.src.tag}";
services.pihole-ftl.settings.webserver = {
domain = cfg.hostName;
port = cfg.ports;

View file

@ -5,7 +5,6 @@
lib.types.enum [
"modern"
"intermediate"
"old"
]
);
default = null;
@ -28,10 +27,6 @@
: General-purpose servers with a variety of clients, recommended for
almost all systems
old
: Compatible with a number of very old clients, & should be used only as
a last resort
The default for all virtual hosts can be set with
services.h2o.defaultTLSRecommendations, but this value can be overridden
on a per-host basis using services.h2o.hosts.<name>.tls.recommmendations.

View file

@ -84,8 +84,8 @@ let
# other settings with the tests @
# `nixos/tests/web-servers/h2o/tls-recommendations.nix`
# & run with `nix-build -A nixosTests.h2o.tls-recommendations`
version = "5.7";
git_tag = "v5.7.1";
version = "6.0";
git_tag = "v6.0";
guidelinesJSON =
lib.pipe
{
@ -93,7 +93,7 @@ let
"https://ssl-config.mozilla.org/guidelines/${version}.json"
"https://raw.githubusercontent.com/mozilla/ssl-config-generator/refs/tags/${git_tag}/src/static/guidelines/${version}.json"
];
sha256 = "sha256:1mj2pcb1hg7q2wpgdq3ac8pc2q64wvwvwlkb9xjmdd9jm4hiyny7";
sha256 = "sha256-aHdzLNPo4c6jlbS+Fg3R0X5VcdPKtUky0oX5Q7Y94SQ=";
}
[
pkgs.fetchurl

View file

@ -72,6 +72,7 @@ in
proxy_pass http://unix:${cfgAuth.socketPath};
proxy_pass_request_body off;
proxy_set_header Content-Length "";
# Upstream uses $http_host here, but we are using gixy to check nginx configurations
# gixy wants us to use $host: https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md

View file

@ -18,12 +18,9 @@ let
cfg = config.services.vinyl-cache;
# Vinyl Cache has very strong opinions and very complicated code around handling
# the stateDir. After a lot of back and forth, we decided that we a)
# do not want a configurable option here, as most of the handling depends
# on the version and the compile time options. Putting everything into
# /var/run (RAM backed) is absolutely recommended by Vinyl Cache anyways.
# We do need to pay attention to the version-dependend variations, though!
stateDir = "/var/run/vinyld";
# the stateDir. After a lot of back and forth, we decided to set the stateDir
# at compile time and let the package expose the particular path as passthru.
stateDir = cfg.package.stateDir;
# from --help:
# -a [<name>=]address[:port][,proto] # HTTP listen address and port
@ -183,13 +180,13 @@ in
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/vinyld ${commandLineAddresses} -n ${stateDir} -F ${cfg.extraCommandLine} ${commandLine}";
ExecStart = "${cfg.package}/bin/vinyld ${commandLineAddresses} -F ${cfg.extraCommandLine} ${commandLine}";
Restart = "always";
RestartSec = "5s";
User = "vinyl-cache";
Group = "vinyl-cache";
DynamicUser = true;
RuntimeDirectory = lib.removePrefix "/var/run/" stateDir;
RuntimeDirectory = lib.removePrefix "/run/" stateDir;
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true;
LimitNOFILE = 131072;
@ -221,6 +218,10 @@ in
assertion = cfg.package.pname == "vinyl-cache";
message = "services.vinyl-cache only supports Vinyl Cache. Please use services.varnish.";
}
{
assertion = lib.strings.hasPrefix "/run/" stateDir;
message = "The vinyl-cache NixOS mosule only supports statedirs in /run/, but vinyl-cache package was compiled with ${stateDir}.";
}
];
})
(lib.mkIf (cfg.enable && cfg.enableFileLogging) {

View file

@ -42,7 +42,7 @@ let
${config.system.systemBuilderCommands}
cp "$extraDependenciesPath" "$out/extra-dependencies"
printf "%s " "''${extraDependencies[@]}" > "$out/extra-dependencies"
${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
${config.boot.bootspec.writer}
@ -60,7 +60,6 @@ let
name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
preferLocalBuild = true;
allowSubstitutes = false;
passAsFile = [ "extraDependencies" ];
buildCommand = systemBuilder;
systemd = config.systemd.package;
@ -70,6 +69,9 @@ let
inherit (config.system) extraDependencies;
}
// config.system.systemBuilderArgs
// {
__structuredAttrs = true;
}
);
# Handle assertions and warnings

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