[release-24.11] Full treewide Nix format and enforcement [skip treewide] (#395018)

This commit is contained in:
Silvan Mosberger 2025-04-01 20:37:11 +02:00 committed by GitHub
commit 8e03b4396b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1791 changed files with 1009935 additions and 534064 deletions

View file

@ -209,3 +209,6 @@ ce21e97a1f20dee15da85c084f9d1148d84f853b
# treewide nixfmt reformat pass 1, master, staging and staging-next # treewide nixfmt reformat pass 1, master, staging and staging-next
d9d87c51960050e89c79e4025082ed965e770d68 d9d87c51960050e89c79e4025082ed965e770d68
# treewide format of all Nix files
14182c19701221692b84e7428e5b7281b099967a # !autorebase nix-shell --run treefmt

View file

@ -1,8 +1,5 @@
# This file was copied mostly from check-maintainers-sorted.yaml. # NOTE: Formatting with the RFC-style nixfmt command is not yet stable.
# NOTE: Formatting with the RFC-style nixfmt command is not yet stable. See # See https://github.com/NixOS/rfcs/pull/166.
# https://github.com/NixOS/rfcs/pull/166.
# Because of this, this action is not yet enabled for all files -- only for
# those who have opted in.
name: Check that Nix files are formatted name: Check that Nix files are formatted
@ -20,80 +17,27 @@ jobs:
name: nixfmt-check name: nixfmt-check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: get-merge-commit needs: get-merge-commit
if: "needs.get-merge-commit.outputs.mergedSha && !contains(github.event.pull_request.title, '[skip treewide]')" if: needs.get-merge-commit.outputs.mergedSha
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
ref: ${{ needs.get-merge-commit.outputs.mergedSha }} ref: ${{ needs.get-merge-commit.outputs.mergedSha }}
# Fetches the merge commit and its parents
fetch-depth: 2
- name: Checking out target branch
run: |
target=$(mktemp -d)
targetRev=$(git rev-parse HEAD^1)
git worktree add "$target" "$targetRev"
echo "targetRev=$targetRev" >> "$GITHUB_ENV"
echo "target=$target" >> "$GITHUB_ENV"
- name: Get Nixpkgs revision for nixfmt
run: |
# pin to a commit from nixpkgs-unstable to avoid e.g. building nixfmt
# from staging
# This should not be a URL, because it would allow PRs to run arbitrary code in CI!
rev=$(jq -r .rev ci/pinned-nixpkgs.json)
echo "url=https://github.com/NixOS/nixpkgs/archive/$rev.tar.gz" >> "$GITHUB_ENV"
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31 - uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
with: with:
extra_nix_config: sandbox = true extra_nix_config: sandbox = true
nix_path: nixpkgs=${{ env.url }}
- name: Install nixfmt - name: Check that Nix files are formatted
run: "nix-env -f '<nixpkgs>' -iAP nixfmt-rfc-style"
- name: Check that Nix files are formatted according to the RFC style
run: | run: |
unformattedFiles=() # Note that it's fine to run this on untrusted code because:
# - There's no secrets accessible here
# TODO: Make this more parallel # - The build is sandboxed
if ! nix-build ci -A fmt.check; then
# Loop through all Nix files touched by the PR echo "Some Nix files are not properly formatted"
while readarray -d '' -n 2 entry && (( ${#entry[@]} != 0 )); do echo "Please format them by going to the Nixpkgs root directory and running one of:"
type=${entry[0]} echo " nix-shell --run treefmt"
file=${entry[1]} echo " nix develop --command treefmt"
case $type in echo " nix fmt"
A*)
source=""
dest=$file
;;
M*)
source=$file
dest=$file
;;
C*|R*)
source=$file
read -r -d '' dest
;;
*)
echo "Ignoring file $file with type $type"
continue
esac
# Ignore files that weren't already formatted
if [[ -n "$source" ]] && ! nixfmt --check ${{ env.target }}/"$source" 2>/dev/null; then
echo "Ignoring file $file because it's not formatted in the target commit"
elif ! nixfmt --check "$dest"; then
unformattedFiles+=("$dest")
fi
done < <(git diff -z --name-status ${{ env.targetRev }} -- '*.nix')
if (( "${#unformattedFiles[@]}" > 0 )); then
echo "Some new/changed Nix files are not properly formatted"
echo "Please format them using the Nixpkgs-specific \`nixfmt\` by going to the Nixpkgs root directory, running \`nix-shell\`, then:"
echo
echo "nixfmt ${unformattedFiles[*]@Q}"
echo
echo "Make sure your branch is up to date with master; rebase if not." echo "Make sure your branch is up to date with master; rebase if not."
echo "If you're having trouble, please ping @NixOS/nix-formatting" echo "If you're having trouble, please ping @NixOS/nix-formatting"
exit 1 exit 1

View file

@ -531,14 +531,31 @@ If you removed packages or made some major NixOS changes, write about it in the
Names of files and directories should be in lowercase, with dashes between words — not in camel case. For instance, it should be `all-packages.nix`, not `allPackages.nix` or `AllPackages.nix`. Names of files and directories should be in lowercase, with dashes between words — not in camel case. For instance, it should be `all-packages.nix`, not `allPackages.nix` or `AllPackages.nix`.
### Formatting
CI [enforces](./.github/workflows/check-nix-format.yml) all Nix files to be
formatted using the [official Nix formatter](https://github.com/NixOS/nixfmt).
You can ensure this locally using either of these commands:
```
nix-shell --run treefmt
nix develop --command treefmt
nix fmt
```
If you're starting your editor in `nix-shell` or `nix develop`,
you can also set it up to automatically format the file with `treefmt` on save.
If you have any problems with formatting, please ping the
[formatting team](https://nixos.org/community/teams/formatting/) via
[@NixOS/nix-formatting](https://github.com/orgs/NixOS/teams/nix-formatting).
### Syntax ### Syntax
- Set up [editorconfig](https://editorconfig.org/) for your editor, such that [the settings](./.editorconfig) are automatically applied. - Set up [editorconfig](https://editorconfig.org/) for your editor, such that [the settings](./.editorconfig) are automatically applied.
- Use `lowerCamelCase` for variable names, not `UpperCamelCase`. Note, this rule does not apply to package attribute names, which instead follow the rules in [package naming](./pkgs/README.md#package-naming). - Use `lowerCamelCase` for variable names, not `UpperCamelCase`. Note, this rule does not apply to package attribute names, which instead follow the rules in [package naming](./pkgs/README.md#package-naming).
- New files must be formatted by entering the `nix-shell` from the repository root and running `nixfmt`.
- Functions should list their expected arguments as precisely as possible. That is, write - Functions should list their expected arguments as precisely as possible. That is, write
```nix ```nix

View file

@ -21,9 +21,52 @@ let
config = { }; config = { };
overlays = [ ]; overlays = [ ];
}; };
fmt =
let
treefmtNixSrc = fetchTarball {
# Master at 2025-02-12
url = "https://github.com/numtide/treefmt-nix/archive/4f09b473c936d41582dd744e19f34ec27592c5fd.tar.gz";
sha256 = "051vh6raskrxw5k6jncm8zbk9fhbzgm1gxpq9gm5xw1b6wgbgcna";
};
treefmtEval = (import treefmtNixSrc).evalModule pkgs {
# Important: The auto-rebase script uses `git filter-branch --tree-filter`,
# which creates trees within the Git repository under `.git-rewrite/t`,
# notably without having a `.git` themselves.
# So if this projectRootFile were the default `.git/config`,
# having the auto-rebase script use treefmt on such a tree would make it
# format all files in the _parent_ Git tree as well.
projectRootFile = ".git-blame-ignore-revs";
# Be a bit more verbose by default, so we can see progress happening
settings.verbose = 1;
# By default it's info, which is too noisy since we have many unmatched files
settings.on-unmatched = "debug";
# This uses nixfmt-rfc-style underneath,
# the default formatter for Nix code.
# See https://github.com/NixOS/nixfmt
programs.nixfmt.enable = true;
};
fs = pkgs.lib.fileset;
nixFilesSrc = fs.toSource {
root = ../.;
fileset = fs.difference (fs.unions [
(fs.fileFilter (file: file.hasExt "nix") ../.)
../.git-blame-ignore-revs
]) (fs.maybeMissing ../.git);
};
in
{
shell = treefmtEval.config.build.devShell;
pkg = treefmtEval.config.build.wrapper;
check = treefmtEval.config.build.check nixFilesSrc;
};
in in
{ {
inherit pkgs; inherit pkgs fmt;
requestReviews = pkgs.callPackage ./request-reviews { }; requestReviews = pkgs.callPackage ./request-reviews { };
codeownersValidator = pkgs.callPackage ./codeowners-validator { }; codeownersValidator = pkgs.callPackage ./codeowners-validator { };
eval = pkgs.callPackage ./eval { }; eval = pkgs.callPackage ./eval { };

255
flake.nix
View file

@ -3,17 +3,21 @@
{ {
description = "A collection of packages for the Nix package manager"; description = "A collection of packages for the Nix package manager";
outputs = { self }: outputs =
{ self }:
let let
libVersionInfoOverlay = import ./lib/flake-version-info.nix self; libVersionInfoOverlay = import ./lib/flake-version-info.nix self;
lib = (import ./lib).extend libVersionInfoOverlay; lib = (import ./lib).extend libVersionInfoOverlay;
forAllSystems = lib.genAttrs lib.systems.flakeExposed; forAllSystems = lib.genAttrs lib.systems.flakeExposed;
jobs = forAllSystems (system: import ./pkgs/top-level/release.nix { jobs = forAllSystems (
nixpkgs = self; system:
inherit system; import ./pkgs/top-level/release.nix {
}); nixpkgs = self;
inherit system;
}
);
in in
{ {
/** /**
@ -24,116 +28,148 @@
- `lib.nixos` for other NixOS-provided functionality, such as [`runTest`](https://nixos.org/manual/nixos/unstable/#sec-call-nixos-test-outside-nixos) - `lib.nixos` for other NixOS-provided functionality, such as [`runTest`](https://nixos.org/manual/nixos/unstable/#sec-call-nixos-test-outside-nixos)
*/ */
lib = lib.extend (final: prev: { lib = lib.extend (
final: prev: {
/** /**
Other NixOS-provided functionality, such as [`runTest`](https://nixos.org/manual/nixos/unstable/#sec-call-nixos-test-outside-nixos). Other NixOS-provided functionality, such as [`runTest`](https://nixos.org/manual/nixos/unstable/#sec-call-nixos-test-outside-nixos).
See also `lib.nixosSystem`. See also `lib.nixosSystem`.
*/ */
nixos = import ./nixos/lib { lib = final; }; nixos = import ./nixos/lib { lib = final; };
/** /**
Create a NixOS system configuration. Create a NixOS system configuration.
Example: Example:
lib.nixosSystem { lib.nixosSystem {
modules = [ ./configuration.nix ]; modules = [ ./configuration.nix ];
}
Inputs:
- `modules` (list of paths or inline modules): The NixOS modules to include in the system configuration.
- `specialArgs` (attribute set): Extra arguments to pass to all modules, that are available in `imports` but can not be extended or overridden by the `modules`.
- `modulesLocation` (path): A default location for modules that aren't passed by path, used for error messages.
Legacy inputs:
- `system`: Legacy alias for `nixpkgs.hostPlatform`, but this is already set in the generated `hardware-configuration.nix`, included by `configuration.nix`.
- `pkgs`: Legacy alias for `nixpkgs.pkgs`; use `nixpkgs.pkgs` and `nixosModules.readOnlyPkgs` instead.
*/
nixosSystem =
args:
import ./nixos/lib/eval-config.nix (
{
lib = final;
# Allow system to be set modularly in nixpkgs.system.
# We set it to null, to remove the "legacy" entrypoint's
# non-hermetic default.
system = null;
modules = args.modules ++ [
# This module is injected here since it exposes the nixpkgs self-path in as
# constrained of contexts as possible to avoid more things depending on it and
# introducing unnecessary potential fragility to changes in flakes itself.
#
# See: failed attempt to make pkgs.path not copy when using flakes:
# https://github.com/NixOS/nixpkgs/pull/153594#issuecomment-1023287913
(
{
config,
pkgs,
lib,
...
}:
{
config.nixpkgs.flake.source = self.outPath;
}
)
];
} }
// builtins.removeAttrs args [ "modules" ]
);
}
);
Inputs: checks = forAllSystems (
system:
- `modules` (list of paths or inline modules): The NixOS modules to include in the system configuration. {
tarball = jobs.${system}.tarball;
- `specialArgs` (attribute set): Extra arguments to pass to all modules, that are available in `imports` but can not be extended or overridden by the `modules`. }
//
- `modulesLocation` (path): A default location for modules that aren't passed by path, used for error messages. lib.optionalAttrs
(
Legacy inputs: self.legacyPackages.${system}.stdenv.hostPlatform.isLinux
# Exclude power64 due to "libressl is not available on the requested hostPlatform" with hostPlatform being power64
- `system`: Legacy alias for `nixpkgs.hostPlatform`, but this is already set in the generated `hardware-configuration.nix`, included by `configuration.nix`. && !self.legacyPackages.${system}.targetPlatform.isPower64
- `pkgs`: Legacy alias for `nixpkgs.pkgs`; use `nixpkgs.pkgs` and `nixosModules.readOnlyPkgs` instead. # Exclude armv6l-linux due to "cannot bootstrap GHC on this platform ('armv6l-linux' with libc 'defaultLibc')"
*/ && system != "armv6l-linux"
nixosSystem = args: # Exclude riscv64-linux due to "cannot bootstrap GHC on this platform ('riscv64-linux' with libc 'defaultLibc')"
import ./nixos/lib/eval-config.nix ( && system != "riscv64-linux"
)
{ {
lib = final; # Test that ensures that the nixosSystem function can accept a lib argument
# Allow system to be set modularly in nixpkgs.system. # Note: prefer not to extend or modify `lib`, especially if you want to share reusable modules
# We set it to null, to remove the "legacy" entrypoint's # alternatives include: `import` a file, or put a custom library in an option or in `_module.args.<libname>`
# non-hermetic default. nixosSystemAcceptsLib =
system = null; (self.lib.nixosSystem {
pkgs = self.legacyPackages.${system};
modules = args.modules ++ [ lib = self.lib.extend (
# This module is injected here since it exposes the nixpkgs self-path in as final: prev: {
# constrained of contexts as possible to avoid more things depending on it and ifThisFunctionIsMissingTheTestFails = final.id;
# introducing unnecessary potential fragility to changes in flakes itself. }
# );
# See: failed attempt to make pkgs.path not copy when using flakes: modules = [
# https://github.com/NixOS/nixpkgs/pull/153594#issuecomment-1023287913 ./nixos/modules/profiles/minimal.nix
({ config, pkgs, lib, ... }: { (
config.nixpkgs.flake.source = self.outPath; { lib, ... }:
}) lib.ifThisFunctionIsMissingTheTestFails {
]; # Define a minimal config without eval warnings
} // builtins.removeAttrs args [ "modules" ] nixpkgs.hostPlatform = "x86_64-linux";
); boot.loader.grub.enable = false;
}); fileSystems."/".device = "nodev";
# See https://search.nixos.org/options?show=system.stateVersion&query=stateversion
checks = forAllSystems (system: { system.stateVersion = lib.trivial.release; # DON'T do this in real configs!
tarball = jobs.${system}.tarball; }
} // lib.optionalAttrs )
( ];
self.legacyPackages.${system}.stdenv.hostPlatform.isLinux }).config.system.build.toplevel;
# Exclude power64 due to "libressl is not available on the requested hostPlatform" with hostPlatform being power64 }
&& !self.legacyPackages.${system}.targetPlatform.isPower64 );
# Exclude armv6l-linux due to "cannot bootstrap GHC on this platform ('armv6l-linux' with libc 'defaultLibc')"
&& system != "armv6l-linux"
# Exclude riscv64-linux due to "cannot bootstrap GHC on this platform ('riscv64-linux' with libc 'defaultLibc')"
&& system != "riscv64-linux"
)
{
# Test that ensures that the nixosSystem function can accept a lib argument
# Note: prefer not to extend or modify `lib`, especially if you want to share reusable modules
# alternatives include: `import` a file, or put a custom library in an option or in `_module.args.<libname>`
nixosSystemAcceptsLib = (self.lib.nixosSystem {
pkgs = self.legacyPackages.${system};
lib = self.lib.extend (final: prev: {
ifThisFunctionIsMissingTheTestFails = final.id;
});
modules = [
./nixos/modules/profiles/minimal.nix
({ lib, ... }: lib.ifThisFunctionIsMissingTheTestFails {
# Define a minimal config without eval warnings
nixpkgs.hostPlatform = "x86_64-linux";
boot.loader.grub.enable = false;
fileSystems."/".device = "nodev";
# See https://search.nixos.org/options?show=system.stateVersion&query=stateversion
system.stateVersion = lib.trivial.release; # DON'T do this in real configs!
})
];
}).config.system.build.toplevel;
});
htmlDocs = { htmlDocs = {
nixpkgsManual = builtins.mapAttrs (_: jobSet: jobSet.manual) jobs; nixpkgsManual = builtins.mapAttrs (_: jobSet: jobSet.manual) jobs;
nixosManual = (import ./nixos/release-small.nix { nixosManual =
nixpkgs = self; (import ./nixos/release-small.nix {
}).nixos.manual; nixpkgs = self;
}).nixos.manual;
}; };
devShells = forAllSystems (system: devShells = forAllSystems (
{ } // lib.optionalAttrs system:
( { }
# Exclude armv6l-linux because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform" //
system != "armv6l-linux" lib.optionalAttrs
# Exclude riscv64-linux because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform" (
&& system != "riscv64-linux" # Exclude armv6l-linux because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform"
# Exclude FreeBSD because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform" system != "armv6l-linux"
&& !self.legacyPackages.${system}.stdenv.hostPlatform.isFreeBSD # Exclude riscv64-linux because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform"
) && system != "riscv64-linux"
{ # Exclude FreeBSD because "Package ghc-9.6.6 in .../pkgs/development/compilers/ghc/common-hadrian.nix:579 is not available on the requested hostPlatform"
/** A shell to get tooling for Nixpkgs development. See nixpkgs/shell.nix. */ && !self.legacyPackages.${system}.stdenv.hostPlatform.isFreeBSD
default = import ./shell.nix { inherit system; }; )
}); {
/**
A shell to get tooling for Nixpkgs development. See nixpkgs/shell.nix.
*/
default = import ./shell.nix { inherit system; };
}
);
formatter = forAllSystems (system: (import ./ci { inherit system; }).fmt.pkg);
/** /**
A nested structure of [packages](https://nix.dev/manual/nix/latest/glossary#package-attribute-set) and other values. A nested structure of [packages](https://nix.dev/manual/nix/latest/glossary#package-attribute-set) and other values.
@ -154,10 +190,13 @@
evaluation. Evaluating the attribute value tends to require a significant evaluation. Evaluating the attribute value tends to require a significant
amount of computation, even considering lazy evaluation. amount of computation, even considering lazy evaluation.
*/ */
legacyPackages = forAllSystems (system: legacyPackages = forAllSystems (
(import ./. { inherit system; }).extend (final: prev: { system:
lib = prev.lib.extend libVersionInfoOverlay; (import ./. { inherit system; }).extend (
}) final: prev: {
lib = prev.lib.extend libVersionInfoOverlay;
}
)
); );
/** /**
@ -176,7 +215,7 @@
}; };
}; };
}; };
*/ */
nixosModules = { nixosModules = {
notDetected = ./nixos/modules/installer/scan/not-detected.nix; notDetected = ./nixos/modules/installer/scan/not-detected.nix;

File diff suppressed because it is too large Load diff

View file

@ -2,19 +2,45 @@
let let
inherit (builtins) inherit (builtins)
intersectAttrs; intersectAttrs
;
inherit (lib) inherit (lib)
functionArgs isFunction mirrorFunctionArgs isAttrs setFunctionArgs functionArgs
optionalAttrs attrNames filter elemAt concatStringsSep sortOn take length isFunction
filterAttrs optionalString flip pathIsDirectory head pipe isDerivation listToAttrs mirrorFunctionArgs
mapAttrs seq flatten deepSeq warnIf isInOldestRelease extends toFunction id isAttrs
setFunctionArgs
optionalAttrs
attrNames
filter
elemAt
concatStringsSep
sortOn
take
length
filterAttrs
optionalString
flip
pathIsDirectory
head
pipe
isDerivation
listToAttrs
mapAttrs
seq
flatten
deepSeq
warnIf
isInOldestRelease
extends
toFunction
id
; ;
inherit (lib.strings) levenshtein levenshteinAtMost; inherit (lib.strings) levenshtein levenshteinAtMost;
in in
rec { rec {
/** /**
`overrideDerivation drv f` takes a derivation (i.e., the result `overrideDerivation drv f` takes a derivation (i.e., the result
of a call to the builtin function `derivation`) and returns a new of a call to the builtin function `derivation`) and returns a new
@ -40,7 +66,6 @@ rec {
You should in general prefer `drv.overrideAttrs` over this function; You should in general prefer `drv.overrideAttrs` over this function;
see the nixpkgs manual for more information on overriding. see the nixpkgs manual for more information on overriding.
# Inputs # Inputs
`drv` `drv`
@ -74,20 +99,21 @@ rec {
::: :::
*/ */
overrideDerivation = drv: f: overrideDerivation =
drv: f:
let let
newDrv = derivation (drv.drvAttrs // (f drv)); newDrv = derivation (drv.drvAttrs // (f drv));
in flip (extendDerivation (seq drv.drvPath true)) newDrv ( in
{ meta = drv.meta or {}; flip (extendDerivation (seq drv.drvPath true)) newDrv (
passthru = if drv ? passthru then drv.passthru else {}; {
meta = drv.meta or { };
passthru = if drv ? passthru then drv.passthru else { };
} }
// // (drv.passthru or { })
(drv.passthru or {}) // optionalAttrs (drv ? __spliced) {
// __spliced = { } // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
optionalAttrs (drv ? __spliced) { }
__spliced = {} // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced); );
});
/** /**
`makeOverridable` takes a function from attribute set to attribute set and `makeOverridable` takes a function from attribute set to attribute set and
@ -97,7 +123,6 @@ rec {
Please refer to documentation on [`<pkg>.overrideDerivation`](#sec-pkg-overrideDerivation) to learn about `overrideDerivation` and caveats Please refer to documentation on [`<pkg>.overrideDerivation`](#sec-pkg-overrideDerivation) to learn about `overrideDerivation` and caveats
related to its use. related to its use.
# Inputs # Inputs
`f` `f`
@ -128,37 +153,42 @@ rec {
::: :::
*/ */
makeOverridable = f: makeOverridable =
f:
let let
# Creates a functor with the same arguments as f # Creates a functor with the same arguments as f
mirrorArgs = mirrorFunctionArgs f; mirrorArgs = mirrorFunctionArgs f;
in in
mirrorArgs (origArgs: mirrorArgs (
let origArgs:
result = f origArgs; let
result = f origArgs;
# Changes the original arguments with (potentially a function that returns) a set of new attributes # Changes the original arguments with (potentially a function that returns) a set of new attributes
overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs); overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs);
# Re-call the function but with different arguments # Re-call the function but with different arguments
overrideArgs = mirrorArgs (newArgs: makeOverridable f (overrideWith newArgs)); overrideArgs = mirrorArgs (newArgs: makeOverridable f (overrideWith newArgs));
# Change the result of the function call by applying g to it # Change the result of the function call by applying g to it
overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs; overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs;
in in
if isAttrs result then if isAttrs result then
result // { result
// {
override = overrideArgs; override = overrideArgs;
overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv); overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv);
${if result ? overrideAttrs then "overrideAttrs" else null} = fdrv: ${if result ? overrideAttrs then "overrideAttrs" else null} =
overrideResult (x: x.overrideAttrs fdrv); fdrv: overrideResult (x: x.overrideAttrs fdrv);
} }
else if isFunction result then else if isFunction result then
# Transform the result into a functor while propagating its arguments # Transform the result into a functor while propagating its arguments
setFunctionArgs result (functionArgs result) // { setFunctionArgs result (functionArgs result)
// {
override = overrideArgs; override = overrideArgs;
} }
else result); else
result
);
/** /**
Call the package function in the file `fn` with the required Call the package function in the file `fn` with the required
@ -188,7 +218,6 @@ rec {
<!-- TODO: Apply "Example:" tag to the examples above --> <!-- TODO: Apply "Example:" tag to the examples above -->
# Inputs # Inputs
`autoArgs` `autoArgs`
@ -209,7 +238,8 @@ rec {
callPackageWith :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a callPackageWith :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a
``` ```
*/ */
callPackageWith = autoArgs: fn: args: callPackageWith =
autoArgs: fn: args:
let let
f = if isFunction fn then fn else import fn; f = if isFunction fn then fn else import fn;
fargs = functionArgs f; fargs = functionArgs f;
@ -222,59 +252,72 @@ rec {
# wouldn't be passed to it # wouldn't be passed to it
missingArgs = missingArgs =
# Filter out arguments that have a default value # Filter out arguments that have a default value
(filterAttrs (name: value: ! value) (
# Filter out arguments that would be passed filterAttrs (name: value: !value)
(removeAttrs fargs (attrNames allArgs))); # Filter out arguments that would be passed
(removeAttrs fargs (attrNames allArgs))
);
# Get a list of suggested argument names for a given missing one # Get a list of suggested argument names for a given missing one
getSuggestions = arg: pipe (autoArgs // args) [ getSuggestions =
attrNames arg:
# Only use ones that are at most 2 edits away. While mork would work, pipe (autoArgs // args) [
# levenshteinAtMost is only fast for 2 or less. attrNames
(filter (levenshteinAtMost 2 arg)) # Only use ones that are at most 2 edits away. While mork would work,
# Put strings with shorter distance first # levenshteinAtMost is only fast for 2 or less.
(sortOn (levenshtein arg)) (filter (levenshteinAtMost 2 arg))
# Only take the first couple results # Put strings with shorter distance first
(take 3) (sortOn (levenshtein arg))
# Quote all entries # Only take the first couple results
(map (x: "\"" + x + "\"")) (take 3)
]; # Quote all entries
(map (x: "\"" + x + "\""))
];
prettySuggestions = suggestions: prettySuggestions =
if suggestions == [] then "" suggestions:
else if length suggestions == 1 then ", did you mean ${elemAt suggestions 0}?" if suggestions == [ ] then
else ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?"; ""
else if length suggestions == 1 then
", did you mean ${elemAt suggestions 0}?"
else
", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";
errorForArg = arg: errorForArg =
arg:
let let
loc = builtins.unsafeGetAttrPos arg fargs; loc = builtins.unsafeGetAttrPos arg fargs;
# loc' can be removed once lib/minver.nix is >2.3.4, since that includes # loc' can be removed once lib/minver.nix is >2.3.4, since that includes
# https://github.com/NixOS/nix/pull/3468 which makes loc be non-null # https://github.com/NixOS/nix/pull/3468 which makes loc be non-null
loc' = if loc != null then loc.file + ":" + toString loc.line loc' =
else if ! isFunction fn then if loc != null then
loc.file + ":" + toString loc.line
else if !isFunction fn then
toString fn + optionalString (pathIsDirectory fn) "/default.nix" toString fn + optionalString (pathIsDirectory fn) "/default.nix"
else "<unknown location>"; else
in "Function called without required argument \"${arg}\" at " "<unknown location>";
in
"Function called without required argument \"${arg}\" at "
+ "${loc'}${prettySuggestions (getSuggestions arg)}"; + "${loc'}${prettySuggestions (getSuggestions arg)}";
# Only show the error for the first missing argument # Only show the error for the first missing argument
error = errorForArg (head (attrNames missingArgs)); error = errorForArg (head (attrNames missingArgs));
in if missingArgs == {} in
then makeOverridable f allArgs if missingArgs == { } then
# This needs to be an abort so it can't be caught with `builtins.tryEval`, makeOverridable f allArgs
# which is used by nix-env and ofborg to filter out packages that don't evaluate. # This needs to be an abort so it can't be caught with `builtins.tryEval`,
# This way we're forced to fix such errors in Nixpkgs, # which is used by nix-env and ofborg to filter out packages that don't evaluate.
# which is especially relevant with allowAliases = false # This way we're forced to fix such errors in Nixpkgs,
else abort "lib.customisation.callPackageWith: ${error}"; # which is especially relevant with allowAliases = false
else
abort "lib.customisation.callPackageWith: ${error}";
/** /**
Like callPackage, but for a function that returns an attribute Like callPackage, but for a function that returns an attribute
set of derivations. The override function is added to the set of derivations. The override function is added to the
individual attributes. individual attributes.
# Inputs # Inputs
`autoArgs` `autoArgs`
@ -295,7 +338,8 @@ rec {
callPackagesWith :: AttrSet -> ((AttrSet -> AttrSet) | Path) -> AttrSet -> AttrSet callPackagesWith :: AttrSet -> ((AttrSet -> AttrSet) | Path) -> AttrSet -> AttrSet
``` ```
*/ */
callPackagesWith = autoArgs: fn: args: callPackagesWith =
autoArgs: fn: args:
let let
f = if isFunction fn then fn else import fn; f = if isFunction fn then fn else import fn;
auto = intersectAttrs (functionArgs f) autoArgs; auto = intersectAttrs (functionArgs f) autoArgs;
@ -304,18 +348,19 @@ rec {
pkgs = f origArgs; pkgs = f origArgs;
mkAttrOverridable = name: _: makeOverridable (mirrorArgs (newArgs: (f newArgs).${name})) origArgs; mkAttrOverridable = name: _: makeOverridable (mirrorArgs (newArgs: (f newArgs).${name})) origArgs;
in in
if isDerivation pkgs then throw if isDerivation pkgs then
("function `callPackages` was called on a *single* derivation " throw (
+ ''"${pkgs.name or "<unknown-name>"}";'' "function `callPackages` was called on a *single* derivation "
+ " did you mean to use `callPackage` instead?") + ''"${pkgs.name or "<unknown-name>"}";''
else mapAttrs mkAttrOverridable pkgs; + " did you mean to use `callPackage` instead?"
)
else
mapAttrs mkAttrOverridable pkgs;
/** /**
Add attributes to each output of a derivation without changing Add attributes to each output of a derivation without changing
the derivation itself and check a given condition when evaluating. the derivation itself and check a given condition when evaluating.
# Inputs # Inputs
`condition` `condition`
@ -336,34 +381,48 @@ rec {
extendDerivation :: Bool -> Any -> Derivation -> Derivation extendDerivation :: Bool -> Any -> Derivation -> Derivation
``` ```
*/ */
extendDerivation = condition: passthru: drv: extendDerivation =
condition: passthru: drv:
let let
outputs = drv.outputs or [ "out" ]; outputs = drv.outputs or [ "out" ];
commonAttrs = drv // (listToAttrs outputsList) // commonAttrs =
({ all = map (x: x.value) outputsList; }) // passthru; drv // (listToAttrs outputsList) // ({ all = map (x: x.value) outputsList; }) // passthru;
outputToAttrListElement = outputName: outputToAttrListElement = outputName: {
{ name = outputName; name = outputName;
value = commonAttrs // { value =
commonAttrs
// {
inherit (drv.${outputName}) type outputName; inherit (drv.${outputName}) type outputName;
outputSpecified = true; outputSpecified = true;
drvPath = assert condition; drv.${outputName}.drvPath; drvPath =
outPath = assert condition; drv.${outputName}.outPath; assert condition;
} // drv.${outputName}.drvPath;
outPath =
assert condition;
drv.${outputName}.outPath;
}
//
# TODO: give the derivation control over the outputs. # TODO: give the derivation control over the outputs.
# `overrideAttrs` may not be the only attribute that needs # `overrideAttrs` may not be the only attribute that needs
# updating when switching outputs. # updating when switching outputs.
optionalAttrs (passthru?overrideAttrs) { optionalAttrs (passthru ? overrideAttrs) {
# TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing. # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing.
overrideAttrs = f: (passthru.overrideAttrs f).${outputName}; overrideAttrs = f: (passthru.overrideAttrs f).${outputName};
}; };
}; };
outputsList = map outputToAttrListElement outputs; outputsList = map outputToAttrListElement outputs;
in commonAttrs // { in
drvPath = assert condition; drv.drvPath; commonAttrs
outPath = assert condition; drv.outPath; // {
drvPath =
assert condition;
drv.drvPath;
outPath =
assert condition;
drv.outPath;
}; };
/** /**
@ -372,7 +431,6 @@ rec {
result to ensure that there are no thunks kept alive to prevent result to ensure that there are no thunks kept alive to prevent
garbage collection. garbage collection.
# Inputs # Inputs
`drv` `drv`
@ -385,21 +443,29 @@ rec {
hydraJob :: (Derivation | Null) -> (Derivation | Null) hydraJob :: (Derivation | Null) -> (Derivation | Null)
``` ```
*/ */
hydraJob = drv: hydraJob =
drv:
let let
outputs = drv.outputs or ["out"]; outputs = drv.outputs or [ "out" ];
commonAttrs = commonAttrs =
{ inherit (drv) name system meta; inherit outputs; } {
inherit (drv) name system meta;
inherit outputs;
}
// optionalAttrs (drv._hydraAggregate or false) { // optionalAttrs (drv._hydraAggregate or false) {
_hydraAggregate = true; _hydraAggregate = true;
constituents = map hydraJob (flatten drv.constituents); constituents = map hydraJob (flatten drv.constituents);
} }
// (listToAttrs outputsList); // (listToAttrs outputsList);
makeOutput = outputName: makeOutput =
let output = drv.${outputName}; in outputName:
{ name = outputName; let
output = drv.${outputName};
in
{
name = outputName;
value = commonAttrs // { value = commonAttrs // {
outPath = output.outPath; outPath = output.outPath;
drvPath = output.drvPath; drvPath = output.drvPath;
@ -411,8 +477,8 @@ rec {
outputsList = map makeOutput outputs; outputsList = map makeOutput outputs;
drv' = (head outputsList).value; drv' = (head outputsList).value;
in if drv == null then null else in
deepSeq drv' drv'; if drv == null then null else deepSeq drv' drv';
/** /**
Make an attribute set (a "scope") from functions that take arguments from that same attribute set. Make an attribute set (a "scope") from functions that take arguments from that same attribute set.
@ -538,23 +604,27 @@ rec {
makeScope :: (AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a) -> (AttrSet -> AttrSet) -> scope makeScope :: (AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a) -> (AttrSet -> AttrSet) -> scope
``` ```
*/ */
makeScope = newScope: f: makeScope =
let self = f self // { newScope: f:
newScope = scope: newScope (self // scope); let
callPackage = self.newScope {}; self = f self // {
overrideScope = g: makeScope newScope (extends g f); newScope = scope: newScope (self // scope);
# Remove after 24.11 is released. callPackage = self.newScope { };
overrideScope' = g: warnIf (isInOldestRelease 2311) overrideScope = g: makeScope newScope (extends g f);
# Remove after 24.11 is released.
overrideScope' =
g:
warnIf (isInOldestRelease 2311)
"`overrideScope'` (from `lib.makeScope`) has been renamed to `overrideScope`." "`overrideScope'` (from `lib.makeScope`) has been renamed to `overrideScope`."
(makeScope newScope (extends g f)); (makeScope newScope (extends g f));
packages = f; packages = f;
}; };
in self; in
self;
/** /**
backward compatibility with old uncurried form; deprecated backward compatibility with old uncurried form; deprecated
# Inputs # Inputs
`splicePackages` `splicePackages`
@ -583,9 +653,14 @@ rec {
*/ */
makeScopeWithSplicing = makeScopeWithSplicing =
splicePackages: newScope: otherSplices: keep: extra: f: splicePackages: newScope: otherSplices: keep: extra: f:
makeScopeWithSplicing' makeScopeWithSplicing' { inherit splicePackages newScope; } {
{ inherit splicePackages newScope; } inherit
{ inherit otherSplices keep extra f; }; otherSplices
keep
extra
f
;
};
/** /**
Like makeScope, but aims to support cross compilation. It's still ugly, but Like makeScope, but aims to support cross compilation. It's still ugly, but
@ -612,30 +687,32 @@ rec {
``` ```
*/ */
makeScopeWithSplicing' = makeScopeWithSplicing' =
{ splicePackages {
, newScope splicePackages,
newScope,
}: }:
{ otherSplices {
# Attrs from `self` which won't be spliced. otherSplices,
# Avoid using keep, it's only used for a python hook workaround, added in PR #104201. # Attrs from `self` which won't be spliced.
# ex: `keep = (self: { inherit (self) aAttr; })` # Avoid using keep, it's only used for a python hook workaround, added in PR #104201.
, keep ? (_self: {}) # ex: `keep = (self: { inherit (self) aAttr; })`
# Additional attrs to add to the sets `callPackage`. keep ? (_self: { }),
# When the package is from a subset (but not a subset within a package IS #211340) # Additional attrs to add to the sets `callPackage`.
# within `spliced0` it will be spliced. # When the package is from a subset (but not a subset within a package IS #211340)
# When using an package outside the set but it's available from `pkgs`, use the package from `pkgs.__splicedPackages`. # within `spliced0` it will be spliced.
# If the package is not available within the set or in `pkgs`, such as a package in a let binding, it will not be spliced # When using an package outside the set but it's available from `pkgs`, use the package from `pkgs.__splicedPackages`.
# ex: # If the package is not available within the set or in `pkgs`, such as a package in a let binding, it will not be spliced
# ``` # ex:
# nix-repl> darwin.apple_sdk.frameworks.CoreFoundation # ```
# «derivation ...CoreFoundation-11.0.0.drv» # nix-repl> darwin.apple_sdk.frameworks.CoreFoundation
# nix-repl> darwin.CoreFoundation # «derivation ...CoreFoundation-11.0.0.drv»
# error: attribute 'CoreFoundation' missing # nix-repl> darwin.CoreFoundation
# nix-repl> darwin.callPackage ({ CoreFoundation }: CoreFoundation) { } # error: attribute 'CoreFoundation' missing
# «derivation ...CoreFoundation-11.0.0.drv» # nix-repl> darwin.callPackage ({ CoreFoundation }: CoreFoundation) { }
# ``` # «derivation ...CoreFoundation-11.0.0.drv»
, extra ? (_spliced0: {}) # ```
, f extra ? (_spliced0: { }),
f,
}: }:
let let
spliced0 = splicePackages { spliced0 = splicePackages {
@ -652,14 +729,16 @@ rec {
callPackage = newScope spliced; # == self.newScope {}; callPackage = newScope spliced; # == self.newScope {};
# N.B. the other stages of the package set spliced in are *not* # N.B. the other stages of the package set spliced in are *not*
# overridden. # overridden.
overrideScope = g: (makeScopeWithSplicing' overrideScope =
{ inherit splicePackages newScope; } g:
{ inherit otherSplices keep extra; (makeScopeWithSplicing' { inherit splicePackages newScope; } {
inherit otherSplices keep extra;
f = extends g f; f = extends g f;
}); });
packages = f; packages = f;
}; };
in self; in
self;
/** /**
Define a `mkDerivation`-like function based on another `mkDerivation`-like function. Define a `mkDerivation`-like function based on another `mkDerivation`-like function.

View file

@ -1,179 +1,529 @@
/* Library of low-level helper functions for nix expressions. /*
* Library of low-level helper functions for nix expressions.
* Please implement (mostly) exhaustive unit tests
* for new functions in `./tests.nix`. Please implement (mostly) exhaustive unit tests
*/ for new functions in `./tests.nix`.
*/
let let
inherit (import ./fixed-points.nix { inherit lib; }) makeExtensible; inherit (import ./fixed-points.nix { inherit lib; }) makeExtensible;
lib = makeExtensible (self: let lib = makeExtensible (
callLibs = file: import file { lib = self; }; self:
in { let
callLibs = file: import file { lib = self; };
in
{
# often used, or depending on very little # often used, or depending on very little
trivial = callLibs ./trivial.nix; trivial = callLibs ./trivial.nix;
fixedPoints = callLibs ./fixed-points.nix; fixedPoints = callLibs ./fixed-points.nix;
# datatypes # datatypes
attrsets = callLibs ./attrsets.nix; attrsets = callLibs ./attrsets.nix;
lists = callLibs ./lists.nix; lists = callLibs ./lists.nix;
strings = callLibs ./strings.nix; strings = callLibs ./strings.nix;
stringsWithDeps = callLibs ./strings-with-deps.nix; stringsWithDeps = callLibs ./strings-with-deps.nix;
# packaging # packaging
customisation = callLibs ./customisation.nix; customisation = callLibs ./customisation.nix;
derivations = callLibs ./derivations.nix; derivations = callLibs ./derivations.nix;
maintainers = import ../maintainers/maintainer-list.nix; maintainers = import ../maintainers/maintainer-list.nix;
teams = callLibs ../maintainers/team-list.nix; teams = callLibs ../maintainers/team-list.nix;
meta = callLibs ./meta.nix; meta = callLibs ./meta.nix;
versions = callLibs ./versions.nix; versions = callLibs ./versions.nix;
# module system # module system
modules = callLibs ./modules.nix; modules = callLibs ./modules.nix;
options = callLibs ./options.nix; options = callLibs ./options.nix;
types = callLibs ./types.nix; types = callLibs ./types.nix;
# constants # constants
licenses = callLibs ./licenses.nix; licenses = callLibs ./licenses.nix;
sourceTypes = callLibs ./source-types.nix; sourceTypes = callLibs ./source-types.nix;
systems = callLibs ./systems; systems = callLibs ./systems;
# serialization # serialization
cli = callLibs ./cli.nix; cli = callLibs ./cli.nix;
gvariant = callLibs ./gvariant.nix; gvariant = callLibs ./gvariant.nix;
generators = callLibs ./generators.nix; generators = callLibs ./generators.nix;
# misc # misc
asserts = callLibs ./asserts.nix; asserts = callLibs ./asserts.nix;
debug = callLibs ./debug.nix; debug = callLibs ./debug.nix;
misc = callLibs ./deprecated/misc.nix; misc = callLibs ./deprecated/misc.nix;
# domain-specific # domain-specific
fetchers = callLibs ./fetchers.nix; fetchers = callLibs ./fetchers.nix;
# Eval-time filesystem handling # Eval-time filesystem handling
path = callLibs ./path; path = callLibs ./path;
filesystem = callLibs ./filesystem.nix; filesystem = callLibs ./filesystem.nix;
fileset = callLibs ./fileset; fileset = callLibs ./fileset;
sources = callLibs ./sources.nix; sources = callLibs ./sources.nix;
# back-compat aliases # back-compat aliases
platforms = self.systems.doubles; platforms = self.systems.doubles;
# linux kernel configuration # linux kernel configuration
kernel = callLibs ./kernel.nix; kernel = callLibs ./kernel.nix;
# network # network
network = callLibs ./network; network = callLibs ./network;
# TODO: For consistency, all builtins should also be available from a sub-library; # TODO: For consistency, all builtins should also be available from a sub-library;
# these are the only ones that are currently not # these are the only ones that are currently not
inherit (builtins) addErrorContext isPath trace typeOf unsafeGetAttrPos; inherit (builtins)
inherit (self.trivial) id const pipe concat or and xor bitAnd bitOr bitXor addErrorContext
bitNot boolToString mergeAttrs flip defaultTo mapNullable inNixShell isFloat min max isPath
importJSON importTOML warn warnIf warnIfNot throwIf throwIfNot checkListOfEnum trace
info showWarnings nixpkgsVersion version isInOldestRelease oldestSupportedReleaseIsAtLeast typeOf
mod compare splitByAndCompare seq deepSeq lessThan add sub unsafeGetAttrPos
functionArgs setFunctionArgs isFunction toFunction mirrorFunctionArgs ;
fromHexString toHexString toBaseDigits inPureEvalMode isBool isInt pathExists inherit (self.trivial)
genericClosure readFile; id
inherit (self.fixedPoints) fix fix' converge extends composeExtensions const
composeManyExtensions makeExtensible makeExtensibleWithCustomName pipe
toExtension; concat
inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath or
getAttrFromPath attrVals attrNames attrValues getAttrs catAttrs filterAttrs and
filterAttrsRecursive foldlAttrs foldAttrs collect nameValuePair mapAttrs xor
mapAttrs' mapAttrsToList attrsToList concatMapAttrs mapAttrsRecursive bitAnd
mapAttrsRecursiveCond genAttrs isDerivation toDerivation optionalAttrs bitOr
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil bitXor
recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput getFirstOutput bitNot
getBin getLib getStatic getDev getInclude getMan chooseDevOutputs zipWithNames zip boolToString
recurseIntoAttrs dontRecurseIntoAttrs cartesianProduct cartesianProductOfSets mergeAttrs
mapCartesianProduct updateManyAttrsByPath listToAttrs hasAttr getAttr isAttrs intersectAttrs removeAttrs; flip
inherit (self.lists) singleton forEach map foldr fold foldl foldl' imap0 imap1 defaultTo
filter ifilter0 concatMap flatten remove findSingle findFirst any all count mapNullable
optional optionals toList range replicate partition zipListsWith zipLists inNixShell
reverseList listDfs toposort sort sortOn naturalSort compareLists isFloat
take drop dropEnd sublist last init min
crossLists unique allUnique intersectLists max
subtractLists mutuallyExclusive groupBy groupBy' concatLists genList importJSON
length head tail elem elemAt isList; importTOML
inherit (self.strings) concatStrings concatMapStrings concatImapStrings warn
stringLength substring isString replaceStrings warnIf
intersperse concatStringsSep concatMapStringsSep warnIfNot
concatImapStringsSep concatLines makeSearchPath makeSearchPathOutput throwIf
makeLibraryPath makeIncludePath makeBinPath optionalString throwIfNot
hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape checkListOfEnum
escapeShellArg escapeShellArgs info
isStorePath isStringLike showWarnings
isValidPosixName toShellVar toShellVars trim trimWith nixpkgsVersion
escapeRegex escapeURL escapeXML replaceChars lowerChars version
upperChars toLower toUpper addContextFrom splitString isInOldestRelease
removePrefix removeSuffix versionOlder versionAtLeast oldestSupportedReleaseIsAtLeast
getName getVersion match split mod
cmakeOptionType cmakeBool cmakeFeature compare
mesonOption mesonBool mesonEnable splitByAndCompare
nameFromURL enableFeature enableFeatureAs withFeature seq
withFeatureAs fixedWidthString fixedWidthNumber deepSeq
toInt toIntBase10 readPathsFromFile fileContents; lessThan
inherit (self.stringsWithDeps) textClosureList textClosureMap add
noDepEntry fullDepEntry packEntry stringAfter; sub
inherit (self.customisation) overrideDerivation makeOverridable functionArgs
callPackageWith callPackagesWith extendDerivation hydraJob setFunctionArgs
makeScope makeScopeWithSplicing makeScopeWithSplicing' isFunction
extendMkDerivation; toFunction
inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; mirrorFunctionArgs
inherit (self.meta) addMetaAttrs dontDistribute setName updateName fromHexString
appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio toHexString
hiPrioSet licensesSpdx getLicenseFromSpdxId getLicenseFromSpdxIdOr toBaseDigits
getExe getExe'; inPureEvalMode
inherit (self.filesystem) pathType pathIsDirectory pathIsRegularFile isBool
packagesFromDirectoryRecursive; isInt
inherit (self.sources) cleanSourceFilter pathExists
cleanSource sourceByRegex sourceFilesBySuffices genericClosure
commitIdFromGitRepo cleanSourceWith pathHasContext readFile
canCleanSource pathIsGitRepo; ;
inherit (self.modules) evalModules setDefaultModuleLocation inherit (self.fixedPoints)
unifyModuleSyntax applyModuleArgsIfFunction mergeModules fix
mergeModules' mergeOptionDecls mergeDefinitions fix'
pushDownProperties dischargeProperties filterOverrides converge
sortProperties fixupOptionType mkIf mkAssert mkMerge mkOverride extends
mkOptionDefault mkDefault mkImageMediaOverride mkForce mkVMOverride composeExtensions
mkFixStrictness mkOrder mkBefore mkAfter mkAliasDefinitions composeManyExtensions
mkAliasAndWrapDefinitions fixMergeModules mkRemovedOptionModule makeExtensible
mkRenamedOptionModule mkRenamedOptionModuleWith makeExtensibleWithCustomName
mkMergedOptionModule mkChangedOptionModule toExtension
mkAliasOptionModule mkDerivedConfig doRename ;
mkAliasOptionModuleMD; inherit (self.attrsets)
evalOptionValue = lib.warn "External use of `lib.evalOptionValue` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." self.modules.evalOptionValue; attrByPath
inherit (self.options) isOption mkEnableOption mkSinkUndeclaredOptions hasAttrByPath
mergeDefaultOption mergeOneOption mergeEqualOption mergeUniqueOption setAttrByPath
getValues getFiles getAttrFromPath
optionAttrSetToDocList optionAttrSetToDocList' attrVals
scrubOptionValue literalExpression literalExample attrNames
showOption showOptionWithDefLocs showFiles attrValues
unknownModule mkOption mkPackageOption mkPackageOptionMD getAttrs
literalMD; catAttrs
inherit (self.types) isType setType defaultTypeMerge defaultFunctor filterAttrs
isOptionType mkOptionType; filterAttrsRecursive
inherit (self.asserts) foldlAttrs
assertMsg assertOneOf; foldAttrs
inherit (self.debug) traceIf traceVal traceValFn collect
traceSeq traceSeqN traceValSeq nameValuePair
traceValSeqFn traceValSeqN traceValSeqNFn traceFnSeqN mapAttrs
runTests testAllTrue; mapAttrs'
inherit (self.misc) maybeEnv defaultMergeArg defaultMerge foldArgs mapAttrsToList
maybeAttrNullable maybeAttr ifEnable checkFlag getValue attrsToList
checkReqs uniqList uniqListExt condConcat lazyGenericClosure concatMapAttrs
innerModifySumArgs modifySumArgs innerClosePropagation mapAttrsRecursive
closePropagation mapAttrsFlatten nvs setAttr setAttrMerge mapAttrsRecursiveCond
mergeAttrsWithFunc mergeAttrsConcatenateValues genAttrs
mergeAttrsNoOverride mergeAttrByFunc mergeAttrsByFuncDefaults isDerivation
mergeAttrsByFuncDefaultsClean mergeAttrBy toDerivation
fakeHash fakeSha256 fakeSha512 optionalAttrs
nixType imap; zipAttrsWithNames
inherit (self.versions) zipAttrsWith
splitVersion; zipAttrs
}); recursiveUpdateUntil
in lib recursiveUpdate
matchAttrs
mergeAttrsList
overrideExisting
showAttrPath
getOutput
getFirstOutput
getBin
getLib
getStatic
getDev
getInclude
getMan
chooseDevOutputs
zipWithNames
zip
recurseIntoAttrs
dontRecurseIntoAttrs
cartesianProduct
cartesianProductOfSets
mapCartesianProduct
updateManyAttrsByPath
listToAttrs
hasAttr
getAttr
isAttrs
intersectAttrs
removeAttrs
;
inherit (self.lists)
singleton
forEach
map
foldr
fold
foldl
foldl'
imap0
imap1
filter
ifilter0
concatMap
flatten
remove
findSingle
findFirst
any
all
count
optional
optionals
toList
range
replicate
partition
zipListsWith
zipLists
reverseList
listDfs
toposort
sort
sortOn
naturalSort
compareLists
take
drop
dropEnd
sublist
last
init
crossLists
unique
allUnique
intersectLists
subtractLists
mutuallyExclusive
groupBy
groupBy'
concatLists
genList
length
head
tail
elem
elemAt
isList
;
inherit (self.strings)
concatStrings
concatMapStrings
concatImapStrings
stringLength
substring
isString
replaceStrings
intersperse
concatStringsSep
concatMapStringsSep
concatImapStringsSep
concatLines
makeSearchPath
makeSearchPathOutput
makeLibraryPath
makeIncludePath
makeBinPath
optionalString
hasInfix
hasPrefix
hasSuffix
stringToCharacters
stringAsChars
escape
escapeShellArg
escapeShellArgs
isStorePath
isStringLike
isValidPosixName
toShellVar
toShellVars
trim
trimWith
escapeRegex
escapeURL
escapeXML
replaceChars
lowerChars
upperChars
toLower
toUpper
addContextFrom
splitString
removePrefix
removeSuffix
versionOlder
versionAtLeast
getName
getVersion
match
split
cmakeOptionType
cmakeBool
cmakeFeature
mesonOption
mesonBool
mesonEnable
nameFromURL
enableFeature
enableFeatureAs
withFeature
withFeatureAs
fixedWidthString
fixedWidthNumber
toInt
toIntBase10
readPathsFromFile
fileContents
;
inherit (self.stringsWithDeps)
textClosureList
textClosureMap
noDepEntry
fullDepEntry
packEntry
stringAfter
;
inherit (self.customisation)
overrideDerivation
makeOverridable
callPackageWith
callPackagesWith
extendDerivation
hydraJob
makeScope
makeScopeWithSplicing
makeScopeWithSplicing'
extendMkDerivation
;
inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate;
inherit (self.meta)
addMetaAttrs
dontDistribute
setName
updateName
appendToName
mapDerivationAttrset
setPrio
lowPrio
lowPrioSet
hiPrio
hiPrioSet
licensesSpdx
getLicenseFromSpdxId
getLicenseFromSpdxIdOr
getExe
getExe'
;
inherit (self.filesystem)
pathType
pathIsDirectory
pathIsRegularFile
packagesFromDirectoryRecursive
;
inherit (self.sources)
cleanSourceFilter
cleanSource
sourceByRegex
sourceFilesBySuffices
commitIdFromGitRepo
cleanSourceWith
pathHasContext
canCleanSource
pathIsGitRepo
;
inherit (self.modules)
evalModules
setDefaultModuleLocation
unifyModuleSyntax
applyModuleArgsIfFunction
mergeModules
mergeModules'
mergeOptionDecls
mergeDefinitions
pushDownProperties
dischargeProperties
filterOverrides
sortProperties
fixupOptionType
mkIf
mkAssert
mkMerge
mkOverride
mkOptionDefault
mkDefault
mkImageMediaOverride
mkForce
mkVMOverride
mkFixStrictness
mkOrder
mkBefore
mkAfter
mkAliasDefinitions
mkAliasAndWrapDefinitions
fixMergeModules
mkRemovedOptionModule
mkRenamedOptionModule
mkRenamedOptionModuleWith
mkMergedOptionModule
mkChangedOptionModule
mkAliasOptionModule
mkDerivedConfig
doRename
mkAliasOptionModuleMD
;
evalOptionValue = lib.warn "External use of `lib.evalOptionValue` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." self.modules.evalOptionValue;
inherit (self.options)
isOption
mkEnableOption
mkSinkUndeclaredOptions
mergeDefaultOption
mergeOneOption
mergeEqualOption
mergeUniqueOption
getValues
getFiles
optionAttrSetToDocList
optionAttrSetToDocList'
scrubOptionValue
literalExpression
literalExample
showOption
showOptionWithDefLocs
showFiles
unknownModule
mkOption
mkPackageOption
mkPackageOptionMD
literalMD
;
inherit (self.types)
isType
setType
defaultTypeMerge
defaultFunctor
isOptionType
mkOptionType
;
inherit (self.asserts)
assertMsg
assertOneOf
;
inherit (self.debug)
traceIf
traceVal
traceValFn
traceSeq
traceSeqN
traceValSeq
traceValSeqFn
traceValSeqN
traceValSeqNFn
traceFnSeqN
runTests
testAllTrue
;
inherit (self.misc)
maybeEnv
defaultMergeArg
defaultMerge
foldArgs
maybeAttrNullable
maybeAttr
ifEnable
checkFlag
getValue
checkReqs
uniqList
uniqListExt
condConcat
lazyGenericClosure
innerModifySumArgs
modifySumArgs
innerClosePropagation
closePropagation
mapAttrsFlatten
nvs
setAttr
setAttrMerge
mergeAttrsWithFunc
mergeAttrsConcatenateValues
mergeAttrsNoOverride
mergeAttrByFunc
mergeAttrsByFuncDefaults
mergeAttrsByFuncDefaultsClean
mergeAttrBy
fakeHash
fakeSha256
fakeSha512
nixType
imap
;
inherit (self.versions)
splitVersion
;
}
);
in
lib

View file

@ -35,153 +35,212 @@ let
inherit (lib.attrsets) removeAttrs mapAttrsToList; inherit (lib.attrsets) removeAttrs mapAttrsToList;
# returns default if env var is not set # returns default if env var is not set
maybeEnv = name: default: maybeEnv =
let value = builtins.getEnv name; in name: default:
let
value = builtins.getEnv name;
in
if value == "" then default else value; if value == "" then default else value;
defaultMergeArg = x : y: if builtins.isAttrs y then defaultMergeArg = x: y: if builtins.isAttrs y then y else (y x);
y
else
(y x);
defaultMerge = x: y: x // (defaultMergeArg x y); defaultMerge = x: y: x // (defaultMergeArg x y);
foldArgs = merger: f: init: x: foldArgs =
let arg = (merger init (defaultMergeArg init x)); merger: f: init: x:
# now add the function with composed args already applied to the final attrs let
base = (setAttrMerge "passthru" {} (f arg) arg = (merger init (defaultMergeArg init x));
( z: z // { # now add the function with composed args already applied to the final attrs
function = foldArgs merger f arg; base = (
args = (attrByPath ["passthru" "args"] {} z) // x; setAttrMerge "passthru" { } (f arg) (
} )); z:
withStdOverrides = base // { z
override = base.passthru.function; // {
}; function = foldArgs merger f arg;
in args = (attrByPath [ "passthru" "args" ] { } z) // x;
withStdOverrides; }
)
);
withStdOverrides = base // {
override = base.passthru.function;
};
in
withStdOverrides;
# shortcut for attrByPath ["name"] default attrs # shortcut for attrByPath ["name"] default attrs
maybeAttrNullable = maybeAttr; maybeAttrNullable = maybeAttr;
# shortcut for attrByPath ["name"] default attrs # shortcut for attrByPath ["name"] default attrs
maybeAttr = name: default: attrs: attrs.${name} or default; maybeAttr =
name: default: attrs:
attrs.${name} or default;
# Return the second argument if the first one is true or the empty version # Return the second argument if the first one is true or the empty version
# of the second argument. # of the second argument.
ifEnable = cond: val: ifEnable =
if cond then val cond: val:
else if builtins.isList val then [] if cond then
else if builtins.isAttrs val then {} val
else if builtins.isList val then
[ ]
else if builtins.isAttrs val then
{ }
# else if builtins.isString val then "" # else if builtins.isString val then ""
else if val == true || val == false then false else if val == true || val == false then
else null; false
else
null;
# Return true only if there is an attribute and it is true. # Return true only if there is an attribute and it is true.
checkFlag = attrSet: name: checkFlag =
if name == "true" then true else attrSet: name:
if name == "false" then false else if name == "true" then
if (elem name (attrByPath ["flags"] [] attrSet)) then true else true
attrByPath [name] false attrSet ; else if name == "false" then
false
else if (elem name (attrByPath [ "flags" ] [ ] attrSet)) then
true
else
attrByPath [ name ] false attrSet;
# Input : attrSet, [ [name default] ... ], name # Input : attrSet, [ [name default] ... ], name
# Output : its value or default. # Output : its value or default.
getValue = attrSet: argList: name: getValue =
( attrByPath [name] (if checkFlag attrSet name then true else attrSet: argList: name:
if argList == [] then null else (attrByPath [ name ] (
let x = builtins.head argList; in if checkFlag attrSet name then
if (head x) == name then true
(head (tail x)) else if argList == [ ] then
else (getValue attrSet null
(tail argList) name)) attrSet ); else
let
x = builtins.head argList;
in
if (head x) == name then (head (tail x)) else (getValue attrSet (tail argList) name)
) attrSet);
# Input : attrSet, [[name default] ...], [ [flagname reqs..] ... ] # Input : attrSet, [[name default] ...], [ [flagname reqs..] ... ]
# Output : are reqs satisfied? It's asserted. # Output : are reqs satisfied? It's asserted.
checkReqs = attrSet: argList: condList: checkReqs =
( attrSet: argList: condList:
foldr and true (foldr and true (
(map (x: let name = (head x); in map (
x:
((checkFlag attrSet name) -> let
(foldr and true name = (head x);
(map (y: let val=(getValue attrSet argList y); in in
(val!=null) && (val!=false))
(tail x))))) condList));
(
(checkFlag attrSet name)
-> (foldr and true (
map (
y:
let
val = (getValue attrSet argList y);
in
(val != null) && (val != false)
) (tail x)
))
)
) condList
));
# This function has O(n^2) performance. # This function has O(n^2) performance.
uniqList = { inputList, acc ? [] }: uniqList =
let go = xs: acc: {
if xs == [] inputList,
then [] acc ? [ ],
else let x = head xs; }:
y = if elem x acc then [] else [x];
in y ++ go (tail xs) (y ++ acc);
in go inputList acc;
uniqListExt = { inputList,
outputList ? [],
getter ? (x: x),
compare ? (x: y: x==y) }:
if inputList == [] then outputList else
let x = head inputList;
isX = y: (compare (getter y) (getter x));
newOutputList = outputList ++
(if any isX outputList then [] else [x]);
in uniqListExt { outputList = newOutputList;
inputList = (tail inputList);
inherit getter compare;
};
condConcat = name: list: checker:
if list == [] then name else
if checker (head list) then
condConcat
(name + (head (tail list)))
(tail (tail list))
checker
else condConcat
name (tail (tail list)) checker;
lazyGenericClosure = {startSet, operator}:
let let
work = list: doneKeys: result: go =
if list == [] then xs: acc:
if xs == [ ] then
[ ]
else
let
x = head xs;
y = if elem x acc then [ ] else [ x ];
in
y ++ go (tail xs) (y ++ acc);
in
go inputList acc;
uniqListExt =
{
inputList,
outputList ? [ ],
getter ? (x: x),
compare ? (x: y: x == y),
}:
if inputList == [ ] then
outputList
else
let
x = head inputList;
isX = y: (compare (getter y) (getter x));
newOutputList = outputList ++ (if any isX outputList then [ ] else [ x ]);
in
uniqListExt {
outputList = newOutputList;
inputList = (tail inputList);
inherit getter compare;
};
condConcat =
name: list: checker:
if list == [ ] then
name
else if checker (head list) then
condConcat (name + (head (tail list))) (tail (tail list)) checker
else
condConcat name (tail (tail list)) checker;
lazyGenericClosure =
{ startSet, operator }:
let
work =
list: doneKeys: result:
if list == [ ] then
result result
else else
let x = head list; key = x.key; in let
x = head list;
key = x.key;
in
if elem key doneKeys then if elem key doneKeys then
work (tail list) doneKeys result work (tail list) doneKeys result
else else
work (tail list ++ operator x) ([key] ++ doneKeys) ([x] ++ result); work (tail list ++ operator x) ([ key ] ++ doneKeys) ([ x ] ++ result);
in in
work startSet [] []; work startSet [ ] [ ];
innerModifySumArgs = f: x: a: b: if b == null then (f a b) // x else innerModifySumArgs =
innerModifySumArgs f x (a // b); f: x: a: b:
modifySumArgs = f: x: innerModifySumArgs f x {}; if b == null then (f a b) // x else innerModifySumArgs f x (a // b);
modifySumArgs = f: x: innerModifySumArgs f x { };
innerClosePropagation =
acc: xs:
if xs == [ ] then
acc
else
let
y = head xs;
ys = tail xs;
in
if !isAttrs y then
innerClosePropagation acc ys
else
let
acc' = [ y ] ++ acc;
in
innerClosePropagation acc' (uniqList {
inputList =
(maybeAttrNullable "propagatedBuildInputs" [ ] y)
++ (maybeAttrNullable "propagatedNativeBuildInputs" [ ] y)
++ ys;
acc = acc';
});
innerClosePropagation = acc: xs: closePropagationSlow = list: (uniqList { inputList = (innerClosePropagation [ ] list); });
if xs == []
then acc
else let y = head xs;
ys = tail xs;
in if ! isAttrs y
then innerClosePropagation acc ys
else let acc' = [y] ++ acc;
in innerClosePropagation
acc'
(uniqList { inputList = (maybeAttrNullable "propagatedBuildInputs" [] y)
++ (maybeAttrNullable "propagatedNativeBuildInputs" [] y)
++ ys;
acc = acc';
}
);
closePropagationSlow = list: (uniqList {inputList = (innerClosePropagation [] list);});
# This is an optimisation of closePropagation which avoids the O(n^2) behavior # This is an optimisation of closePropagation which avoids the O(n^2) behavior
# Using a list of derivations, it generates the full closure of the propagatedXXXBuildInputs # Using a list of derivations, it generates the full closure of the propagatedXXXBuildInputs
@ -189,28 +248,35 @@ let
# attribute of each derivation. # attribute of each derivation.
# On some benchmarks, it performs up to 15 times faster than closePropagation. # On some benchmarks, it performs up to 15 times faster than closePropagation.
# See https://github.com/NixOS/nixpkgs/pull/194391 for details. # See https://github.com/NixOS/nixpkgs/pull/194391 for details.
closePropagationFast = list: closePropagationFast =
builtins.map (x: x.val) (builtins.genericClosure { list:
startSet = builtins.map (x: { builtins.map (x: x.val) (
key = x.outPath; builtins.genericClosure {
val = x; startSet = builtins.map (x: {
}) (builtins.filter (x: x != null) list); key = x.outPath;
operator = item: val = x;
if !builtins.isAttrs item.val then }) (builtins.filter (x: x != null) list);
[ ] operator =
else item:
builtins.concatMap (x: if !builtins.isAttrs item.val then
if x != null then [{ [ ]
key = x.outPath; else
val = x; builtins.concatMap (
}] else x:
[ ]) ((item.val.propagatedBuildInputs or [ ]) if x != null then
++ (item.val.propagatedNativeBuildInputs or [ ])); [
}); {
key = x.outPath;
val = x;
}
]
else
[ ]
) ((item.val.propagatedBuildInputs or [ ]) ++ (item.val.propagatedNativeBuildInputs or [ ]));
}
);
closePropagation = if builtins ? genericClosure closePropagation = if builtins ? genericClosure then closePropagationFast else closePropagationSlow;
then closePropagationFast
else closePropagationSlow;
# calls a function (f attr value ) for each record item. returns a list # calls a function (f attr value ) for each record item. returns a list
mapAttrsFlatten = warn "lib.misc.mapAttrsFlatten is deprecated, please use lib.attrsets.mapAttrsToList instead." mapAttrsToList; mapAttrsFlatten = warn "lib.misc.mapAttrsFlatten is deprecated, please use lib.attrsets.mapAttrsToList instead." mapAttrsToList;
@ -218,26 +284,29 @@ let
# attribute set containing one attribute # attribute set containing one attribute
nvs = name: value: listToAttrs [ (nameValuePair name value) ]; nvs = name: value: listToAttrs [ (nameValuePair name value) ];
# adds / replaces an attribute of an attribute set # adds / replaces an attribute of an attribute set
setAttr = set: name: v: set // (nvs name v); setAttr =
set: name: v:
set // (nvs name v);
# setAttrMerge (similar to mergeAttrsWithFunc but only merges the values of a particular name) # setAttrMerge (similar to mergeAttrsWithFunc but only merges the values of a particular name)
# setAttrMerge "a" [] { a = [2];} (x: x ++ [3]) -> { a = [2 3]; } # setAttrMerge "a" [] { a = [2];} (x: x ++ [3]) -> { a = [2 3]; }
# setAttrMerge "a" [] { } (x: x ++ [3]) -> { a = [ 3]; } # setAttrMerge "a" [] { } (x: x ++ [3]) -> { a = [ 3]; }
setAttrMerge = name: default: attrs: f: setAttrMerge =
name: default: attrs: f:
setAttr attrs name (f (maybeAttr name default attrs)); setAttr attrs name (f (maybeAttr name default attrs));
# Using f = a: b = b the result is similar to // # Using f = a: b = b the result is similar to //
# merge attributes with custom function handling the case that the attribute # merge attributes with custom function handling the case that the attribute
# exists in both sets # exists in both sets
mergeAttrsWithFunc = f: set1: set2: mergeAttrsWithFunc =
foldr (n: set: if set ? ${n} f: set1: set2:
then setAttr set n (f set.${n} set2.${n}) foldr (n: set: if set ? ${n} then setAttr set n (f set.${n} set2.${n}) else set) (set2 // set1) (
else set ) attrNames set2
(set2 // set1) (attrNames set2); );
# merging two attribute set concatenating the values of same attribute names # merging two attribute set concatenating the values of same attribute names
# eg { a = 7; } { a = [ 2 3 ]; } becomes { a = [ 7 2 3 ]; } # eg { a = 7; } { a = [ 2 3 ]; } becomes { a = [ 7 2 3 ]; }
mergeAttrsConcatenateValues = mergeAttrsWithFunc ( a: b: (toList a) ++ (toList b) ); mergeAttrsConcatenateValues = mergeAttrsWithFunc (a: b: (toList a) ++ (toList b));
# merges attributes using //, if a name exists in both attributes # merges attributes using //, if a name exists in both attributes
# an error will be triggered unless its listed in mergeLists # an error will be triggered unless its listed in mergeLists
@ -246,20 +315,31 @@ let
# merging buildPhase doesn't really make sense. The cases will be rare where appending /prefixing will fit your needs? # merging buildPhase doesn't really make sense. The cases will be rare where appending /prefixing will fit your needs?
# in these cases the first buildPhase will override the second one # in these cases the first buildPhase will override the second one
# ! deprecated, use mergeAttrByFunc instead # ! deprecated, use mergeAttrByFunc instead
mergeAttrsNoOverride = { mergeLists ? ["buildInputs" "propagatedBuildInputs"], mergeAttrsNoOverride =
overrideSnd ? [ "buildPhase" ] {
}: attrs1: attrs2: mergeLists ? [
foldr (n: set: "buildInputs"
setAttr set n ( if set ? ${n} "propagatedBuildInputs"
then # merge ],
if elem n mergeLists # attribute contains list, merge them by concatenating overrideSnd ? [ "buildPhase" ],
then attrs2.${n} ++ attrs1.${n} }:
else if elem n overrideSnd attrs1: attrs2:
then attrs1.${n} foldr (
else throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined" n: set:
else attrs2.${n} # add attribute not existing in attr1 setAttr set n (
)) attrs1 (attrNames attrs2); if set ? ${n} then # merge
if
elem n mergeLists # attribute contains list, merge them by concatenating
then
attrs2.${n} ++ attrs1.${n}
else if elem n overrideSnd then
attrs1.${n}
else
throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined"
else
attrs2.${n} # add attribute not existing in attr1
)
) attrs1 (attrNames attrs2);
# example usage: # example usage:
# mergeAttrByFunc { # mergeAttrByFunc {
@ -272,48 +352,82 @@ let
# { mergeAttrsBy = [...]; buildInputs = [ a b c d ]; } # { mergeAttrsBy = [...]; buildInputs = [ a b c d ]; }
# is used by defaultOverridableDelayableArgs and can be used when composing using # is used by defaultOverridableDelayableArgs and can be used when composing using
# foldArgs, composedArgsAndFun or applyAndFun. Example: composableDerivation in all-packages.nix # foldArgs, composedArgsAndFun or applyAndFun. Example: composableDerivation in all-packages.nix
mergeAttrByFunc = x: y: mergeAttrByFunc =
x: y:
let let
mergeAttrBy2 = { mergeAttrBy = mergeAttrs; } mergeAttrBy2 =
// (maybeAttr "mergeAttrBy" {} x) { mergeAttrBy = mergeAttrs; } // (maybeAttr "mergeAttrBy" { } x) // (maybeAttr "mergeAttrBy" { } y);
// (maybeAttr "mergeAttrBy" {} y); in in
foldr mergeAttrs {} [ foldr mergeAttrs { } [
x y x
(mapAttrs ( a: v: # merge special names using given functions y
if x ? ${a} (mapAttrs
then if y ? ${a} (
then v x.${a} y.${a} # both have attr, use merge func a: v: # merge special names using given functions
else x.${a} # only x has attr if x ? ${a} then
else y.${a} # only y has attr) if y ? ${a} then
) (removeAttrs mergeAttrBy2 v x.${a} y.${a} # both have attr, use merge func
# don't merge attrs which are neither in x nor y else
(filter (a: ! x ? ${a} && ! y ? ${a}) x.${a} # only x has attr
(attrNames mergeAttrBy2)) else
) y.${a} # only y has attr)
)
(
removeAttrs mergeAttrBy2
# don't merge attrs which are neither in x nor y
(filter (a: !x ? ${a} && !y ? ${a}) (attrNames mergeAttrBy2))
)
) )
]; ];
mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; }; mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; };
mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) ["mergeAttrBy"]; mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) [ "mergeAttrBy" ];
# sane defaults (same name as attr name so that inherit can be used) # sane defaults (same name as attr name so that inherit can be used)
mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; } mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; }
listToAttrs (map (n: nameValuePair n concat) listToAttrs (
[ "nativeBuildInputs" "buildInputs" "propagatedBuildInputs" "configureFlags" "prePhases" "postAll" "patches" ]) map (n: nameValuePair n concat) [
// listToAttrs (map (n: nameValuePair n mergeAttrs) [ "passthru" "meta" "cfg" "flags" ]) "nativeBuildInputs"
// listToAttrs (map (n: nameValuePair n (a: b: "${a}\n${b}") ) [ "preConfigure" "postInstall" ]) "buildInputs"
; "propagatedBuildInputs"
"configureFlags"
"prePhases"
"postAll"
"patches"
]
)
// listToAttrs (
map (n: nameValuePair n mergeAttrs) [
"passthru"
"meta"
"cfg"
"flags"
]
)
// listToAttrs (
map (n: nameValuePair n (a: b: "${a}\n${b}")) [
"preConfigure"
"postInstall"
]
);
nixType = x: nixType =
if isAttrs x then x:
if x ? outPath then "derivation" if isAttrs x then
else "attrs" if x ? outPath then "derivation" else "attrs"
else if isFunction x then "function" else if isFunction x then
else if isList x then "list" "function"
else if x == true then "bool" else if isList x then
else if x == false then "bool" "list"
else if x == null then "null" else if x == true then
else if isInt x then "int" "bool"
else "string"; else if x == false then
"bool"
else if x == null then
"null"
else if isInt x then
"int"
else
"string";
/** /**
# Deprecated # Deprecated

View file

@ -242,8 +242,11 @@ in
warnOnInstantiate = warnOnInstantiate =
msg: drv: msg: drv:
let let
drvToWrap = removeAttrs drv [ "meta" "name" "type" ]; drvToWrap = removeAttrs drv [
"meta"
"name"
"type"
];
in in
drv drv // mapAttrs (_: lib.warn msg) drvToWrap;
// mapAttrs (_: lib.warn msg) drvToWrap;
} }

View file

@ -62,23 +62,26 @@ in
pathType = pathType =
builtins.readFileType or builtins.readFileType or
# Nix <2.14 compatibility shim # Nix <2.14 compatibility shim
(path: (
if ! pathExists path path:
if
!pathExists path
# Fail irrecoverably to mimic the historic behavior of this function and # Fail irrecoverably to mimic the historic behavior of this function and
# the new builtins.readFileType # the new builtins.readFileType
then abort "lib.filesystem.pathType: Path ${toString path} does not exist." then
abort "lib.filesystem.pathType: Path ${toString path} does not exist."
# The filesystem root is the only path where `dirOf / == /` and # The filesystem root is the only path where `dirOf / == /` and
# `baseNameOf /` is not valid. We can detect this and directly return # `baseNameOf /` is not valid. We can detect this and directly return
# "directory", since we know the filesystem root can't be anything else. # "directory", since we know the filesystem root can't be anything else.
else if dirOf path == path else if dirOf path == path then
then "directory" "directory"
else (readDir (dirOf path)).${baseNameOf path} else
(readDir (dirOf path)).${baseNameOf path}
); );
/** /**
Whether a path exists and is a directory. Whether a path exists and is a directory.
# Inputs # Inputs
`path` `path`
@ -108,13 +111,11 @@ in
::: :::
*/ */
pathIsDirectory = path: pathIsDirectory = path: pathExists path && pathType path == "directory";
pathExists path && pathType path == "directory";
/** /**
Whether a path exists and is a regular file, meaning not a symlink or any other special file type. Whether a path exists and is a regular file, meaning not a symlink or any other special file type.
# Inputs # Inputs
`path` `path`
@ -144,15 +145,13 @@ in
::: :::
*/ */
pathIsRegularFile = path: pathIsRegularFile = path: pathExists path && pathType path == "regular";
pathExists path && pathType path == "regular";
/** /**
A map of all haskell packages defined in the given path, A map of all haskell packages defined in the given path,
identified by having a cabal file with the same name as the identified by having a cabal file with the same name as the
directory itself. directory itself.
# Inputs # Inputs
`root` `root`
@ -167,25 +166,25 @@ in
*/ */
haskellPathsInDir = haskellPathsInDir =
root: root:
let # Files in the root let
root-files = builtins.attrNames (builtins.readDir root); # Files in the root
# Files with their full paths root-files = builtins.attrNames (builtins.readDir root);
root-files-with-paths = # Files with their full paths
map (file: root-files-with-paths = map (file: {
{ name = file; value = root + "/${file}"; } name = file;
) root-files; value = root + "/${file}";
# Subdirectories of the root with a cabal file. }) root-files;
cabal-subdirs = # Subdirectories of the root with a cabal file.
builtins.filter ({ name, value }: cabal-subdirs = builtins.filter (
builtins.pathExists (value + "/${name}.cabal") { name, value }: builtins.pathExists (value + "/${name}.cabal")
) root-files-with-paths; ) root-files-with-paths;
in builtins.listToAttrs cabal-subdirs; in
builtins.listToAttrs cabal-subdirs;
/** /**
Find the first directory containing a file matching 'pattern' Find the first directory containing a file matching 'pattern'
upward from a given 'file'. upward from a given 'file'.
Returns 'null' if no directories contain a file matching 'pattern'. Returns 'null' if no directories contain a file matching 'pattern'.
# Inputs # Inputs
`pattern` `pattern`
@ -203,30 +202,33 @@ in
``` ```
*/ */
locateDominatingFile = locateDominatingFile =
pattern: pattern: file:
file: let
let go = path: go =
let files = builtins.attrNames (builtins.readDir path); path:
matches = builtins.filter (match: match != null) let
(map (builtins.match pattern) files); files = builtins.attrNames (builtins.readDir path);
in matches = builtins.filter (match: match != null) (map (builtins.match pattern) files);
if builtins.length matches != 0 in
then { inherit path matches; } if builtins.length matches != 0 then
else if path == /. { inherit path matches; }
then null else if path == /. then
else go (dirOf path); null
parent = dirOf file; else
isDir = go (dirOf path);
let base = baseNameOf file; parent = dirOf file;
type = (builtins.readDir parent).${base} or null; isDir =
in file == /. || type == "directory"; let
in go (if isDir then file else parent); base = baseNameOf file;
type = (builtins.readDir parent).${base} or null;
in
file == /. || type == "directory";
in
go (if isDir then file else parent);
/** /**
Given a directory, return a flattened list of all files within it recursively. Given a directory, return a flattened list of all files within it recursively.
# Inputs # Inputs
`dir` `dir`
@ -241,12 +243,15 @@ in
*/ */
listFilesRecursive = listFilesRecursive =
dir: dir:
lib.flatten (lib.mapAttrsToList (name: type: lib.flatten (
if type == "directory" then lib.mapAttrsToList (
lib.filesystem.listFilesRecursive (dir + "/${name}") name: type:
else if type == "directory" then
dir + "/${name}" lib.filesystem.listFilesRecursive (dir + "/${name}")
) (builtins.readDir dir)); else
dir + "/${name}"
) (builtins.readDir dir)
);
/** /**
Transform a directory tree containing package files suitable for Transform a directory tree containing package files suitable for
@ -325,7 +330,6 @@ in
Type: `Path` Type: `Path`
# Type # Type
``` ```
@ -363,49 +367,44 @@ in
let let
# Determine if a directory entry from `readDir` indicates a package or # Determine if a directory entry from `readDir` indicates a package or
# directory of packages. # directory of packages.
directoryEntryIsPackage = basename: type: directoryEntryIsPackage = basename: type: type == "directory" || hasSuffix ".nix" basename;
type == "directory" || hasSuffix ".nix" basename;
# List directory entries that indicate packages in the given `path`. # List directory entries that indicate packages in the given `path`.
packageDirectoryEntries = path: packageDirectoryEntries = path: filterAttrs directoryEntryIsPackage (readDir path);
filterAttrs directoryEntryIsPackage (readDir path);
# Transform a directory entry (a `basename` and `type` pair) into a # Transform a directory entry (a `basename` and `type` pair) into a
# package. # package.
directoryEntryToAttrPair = subdirectory: basename: type: directoryEntryToAttrPair =
subdirectory: basename: type:
let let
path = subdirectory + "/${basename}"; path = subdirectory + "/${basename}";
in in
if type == "regular" if type == "regular" then
then {
{ name = removeSuffix ".nix" basename;
name = removeSuffix ".nix" basename; value = callPackage path { };
value = callPackage path { }; }
} else if type == "directory" then
{
name = basename;
value = packagesFromDirectory path;
}
else else
if type == "directory" throw ''
then
{
name = basename;
value = packagesFromDirectory path;
}
else
throw
''
lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString subdirectory} lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString subdirectory}
''; '';
# Transform a directory into a package (if there's a `package.nix`) or # Transform a directory into a package (if there's a `package.nix`) or
# set of packages (otherwise). # set of packages (otherwise).
packagesFromDirectory = path: packagesFromDirectory =
path:
let let
defaultPackagePath = path + "/package.nix"; defaultPackagePath = path + "/package.nix";
in in
if pathExists defaultPackagePath if pathExists defaultPackagePath then
then callPackage defaultPackagePath { } callPackage defaultPackagePath { }
else mapAttrs' else
(directoryEntryToAttrPair path) mapAttrs' (directoryEntryToAttrPair path) (packageDirectoryEntries path);
(packageDirectoryEntries path);
in in
packagesFromDirectory directory; packagesFromDirectory directory;
} }

View file

@ -35,7 +35,7 @@ let
filter filter
flatten flatten
foldl foldl
functionArgs # Note: not the builtin; considers `__functor` in attrsets. functionArgs # Note: not the builtin; considers `__functor` in attrsets.
gvariant gvariant
hasInfix hasInfix
head head
@ -45,7 +45,7 @@ let
isBool isBool
isDerivation isDerivation
isFloat isFloat
isFunction # Note: not the builtin; considers `__functor` in attrsets. isFunction # Note: not the builtin; considers `__functor` in attrsets.
isInt isInt
isList isList
isPath isPath
@ -73,7 +73,8 @@ let
; ;
## -- HELPER FUNCTIONS & DEFAULTS -- ## -- HELPER FUNCTIONS & DEFAULTS --
in rec { in
rec {
/** /**
Convert a value to a sensible default string representation. Convert a value to a sensible default string representation.
The builtin `toString` function has some strange defaults, The builtin `toString` function has some strange defaults,
@ -87,32 +88,44 @@ in rec {
`v` `v`
: 2\. Function argument : 2\. Function argument
*/ */
mkValueStringDefault = {}: v: mkValueStringDefault =
let err = t: v: abort { }:
("generators.mkValueStringDefault: " + v:
"${t} not supported: ${toPretty {} v}"); let
in if isInt v then toString v err = t: v: abort ("generators.mkValueStringDefault: " + "${t} not supported: ${toPretty { } v}");
in
if isInt v then
toString v
# convert derivations to store paths # convert derivations to store paths
else if isDerivation v then toString v else if isDerivation v then
toString v
# we default to not quoting strings # we default to not quoting strings
else if isString v then v else if isString v then
v
# isString returns "1", which is not a good default # isString returns "1", which is not a good default
else if true == v then "true" else if true == v then
"true"
# here it returns to "", which is even less of a good default # here it returns to "", which is even less of a good default
else if false == v then "false" else if false == v then
else if null == v then "null" "false"
else if null == v then
"null"
# if you have lists you probably want to replace this # if you have lists you probably want to replace this
else if isList v then err "lists" v else if isList v then
err "lists" v
# same as for lists, might want to replace # same as for lists, might want to replace
else if isAttrs v then err "attrsets" v else if isAttrs v then
err "attrsets" v
# functions cant be printed of course # functions cant be printed of course
else if isFunction v then err "functions" v else if isFunction v then
err "functions" v
# Floats currently can't be converted to precise strings, # Floats currently can't be converted to precise strings,
# condition warning on nix version once this isn't a problem anymore # condition warning on nix version once this isn't a problem anymore
# See https://github.com/NixOS/nix/pull/3480 # See https://github.com/NixOS/nix/pull/3480
else if isFloat v then floatToString v else if isFloat v then
else err "this value is" (toString v); floatToString v
else
err "this value is" (toString v);
/** /**
Generate a line of key k and value v, separated by Generate a line of key k and value v, separated by
@ -144,15 +157,15 @@ in rec {
: 4\. Function argument : 4\. Function argument
*/ */
mkKeyValueDefault = { mkKeyValueDefault =
mkValueString ? mkValueStringDefault {} {
}: sep: k: v: mkValueString ? mkValueStringDefault { },
"${escape [sep] k}${sep}${mkValueString v}"; }:
sep: k: v:
"${escape [ sep ] k}${sep}${mkValueString v}";
## -- FILE FORMAT GENERATORS -- ## -- FILE FORMAT GENERATORS --
/** /**
Generate a key-value-style config file from an attrset. Generate a key-value-style config file from an attrset.
@ -168,19 +181,22 @@ in rec {
: indent (optional, default: `""`) : indent (optional, default: `""`)
: Initial indentation level : Initial indentation level
*/ */
toKeyValue = { toKeyValue =
mkKeyValue ? mkKeyValueDefault {} "=", {
listsAsDuplicateKeys ? false, mkKeyValue ? mkKeyValueDefault { } "=",
indent ? "" listsAsDuplicateKeys ? false,
}: indent ? "",
let mkLine = k: v: indent + mkKeyValue k v + "\n"; }:
mkLines = if listsAsDuplicateKeys let
then k: v: map (mkLine k) (if isList v then v else [v]) mkLine = k: v: indent + mkKeyValue k v + "\n";
else k: v: [ (mkLine k v) ]; mkLines =
in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs)); if listsAsDuplicateKeys then
k: v: map (mkLine k) (if isList v then v else [ v ])
else
k: v: [ (mkLine k v) ];
in
attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs));
/** /**
Generate an INI-style config file from an Generate an INI-style config file from an
@ -224,22 +240,27 @@ in rec {
::: :::
*/ */
toINI = { toINI =
mkSectionName ? (name: escape [ "[" "]" ] name), {
mkKeyValue ? mkKeyValueDefault {} "=", mkSectionName ? (name: escape [ "[" "]" ] name),
listsAsDuplicateKeys ? false mkKeyValue ? mkKeyValueDefault { } "=",
}: attrsOfAttrs: listsAsDuplicateKeys ? false,
}:
attrsOfAttrs:
let let
# map function to string for each key val # map function to string for each key val
mapAttrsToStringsSep = sep: mapFn: attrs: mapAttrsToStringsSep =
concatStringsSep sep sep: mapFn: attrs:
(mapAttrsToList mapFn attrs); concatStringsSep sep (mapAttrsToList mapFn attrs);
mkSection = sectName: sectValues: '' mkSection =
sectName: sectValues:
''
[${mkSectionName sectName}] [${mkSectionName sectName}]
'' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; ''
+ toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
in in
# map input to ini sections # map input to ini sections
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
/** /**
Generate an INI-style config file from an attrset Generate an INI-style config file from an attrset
@ -302,15 +323,22 @@ in rec {
`generators.toINI` directly, which only takes `generators.toINI` directly, which only takes
the part in `sections`. the part in `sections`.
*/ */
toINIWithGlobalSection = { toINIWithGlobalSection =
mkSectionName ? (name: escape [ "[" "]" ] name), {
mkKeyValue ? mkKeyValueDefault {} "=", mkSectionName ? (name: escape [ "[" "]" ] name),
listsAsDuplicateKeys ? false mkKeyValue ? mkKeyValueDefault { } "=",
}: { globalSection, sections ? {} }: listsAsDuplicateKeys ? false,
( if globalSection == {} }:
then "" {
else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) globalSection,
+ "\n") sections ? { },
}:
(
if globalSection == { } then
""
else
(toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) + "\n"
)
+ (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections); + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections);
/** /**
@ -348,50 +376,57 @@ in rec {
: Key-value pairs to be converted to a git-config file. : Key-value pairs to be converted to a git-config file.
See: https://git-scm.com/docs/git-config#_variables for possible values. See: https://git-scm.com/docs/git-config#_variables for possible values.
*/ */
toGitINI = attrs: toGitINI =
attrs:
let let
mkSectionName = name: mkSectionName =
name:
let let
containsQuote = hasInfix ''"'' name; containsQuote = hasInfix ''"'' name;
sections = splitString "." name; sections = splitString "." name;
section = head sections; section = head sections;
subsections = tail sections; subsections = tail sections;
subsection = concatStringsSep "." subsections; subsection = concatStringsSep "." subsections;
in if containsQuote || subsections == [ ] then in
name if containsQuote || subsections == [ ] then name else ''${section} "${subsection}"'';
else
''${section} "${subsection}"'';
mkValueString = v: mkValueString =
v:
let let
escapedV = '' escapedV = ''"${replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v}"'';
"${ in
replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v mkValueStringDefault { } (if isString v then escapedV else v);
}"'';
in mkValueStringDefault { } (if isString v then escapedV else v);
# generation for multiple ini values # generation for multiple ini values
mkKeyValue = k: v: mkKeyValue =
let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k; k: v:
in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v)); let
mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k;
in
concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v));
# converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
gitFlattenAttrs = let gitFlattenAttrs =
recurse = path: value: let
if isAttrs value && !isDerivation value then recurse =
mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value path: value:
else if length path > 1 then { if isAttrs value && !isDerivation value then
${concatStringsSep "." (reverseList (tail path))}.${head path} = value; mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
} else { else if length path > 1 then
${head path} = value; {
}; ${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs)); }
else
{
${head path} = value;
};
in
attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
toINI_ = toINI { inherit mkKeyValue mkSectionName; }; toINI_ = toINI { inherit mkKeyValue mkSectionName; };
in in
toINI_ (gitFlattenAttrs attrs); toINI_ (gitFlattenAttrs attrs);
/** /**
mkKeyValueDefault wrapper that handles dconf INI quirks. mkKeyValueDefault wrapper that handles dconf INI quirks.
@ -426,35 +461,39 @@ in rec {
withRecursion = withRecursion =
{ {
depthLimit, depthLimit,
throwOnDepthLimit ? true throwOnDepthLimit ? true,
}: }:
assert isInt depthLimit; assert isInt depthLimit;
let let
specialAttrs = [ specialAttrs = [
"__functor" "__functor"
"__functionArgs" "__functionArgs"
"__toString" "__toString"
"__pretty" "__pretty"
]; ];
stepIntoAttr = evalNext: name: stepIntoAttr = evalNext: name: if elem name specialAttrs then id else evalNext;
if elem name specialAttrs transform =
then id depth:
else evalNext; if depthLimit != null && depth > depthLimit then
transform = depth: if throwOnDepthLimit then
if depthLimit != null && depth > depthLimit then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!"
if throwOnDepthLimit else
then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!" const "<unevaluated>"
else const "<unevaluated>" else
else id; id;
mapAny = depth: v: mapAny =
let depth: v:
evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); let
in evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
if isAttrs v then mapAttrs (stepIntoAttr evalNext) v in
else if isList v then map evalNext v if isAttrs v then
else transform (depth + 1) v; mapAttrs (stepIntoAttr evalNext) v
in else if isList v then
mapAny 0; map evalNext v
else
transform (depth + 1) v;
in
mapAny 0;
/** /**
Pretty print a value, akin to `builtins.trace`. Pretty print a value, akin to `builtins.trace`.
@ -480,68 +519,96 @@ in rec {
Value Value
: The value to be pretty printed : The value to be pretty printed
*/ */
toPretty = { toPretty =
allowPrettyValues ? false, {
multiline ? true, allowPrettyValues ? false,
indent ? "" multiline ? true,
}: indent ? "",
}:
let let
go = indent: v: go =
let introSpace = if multiline then "\n${indent} " else " "; indent: v:
outroSpace = if multiline then "\n${indent}" else " "; let
in if isInt v then toString v introSpace = if multiline then "\n${indent} " else " ";
# toString loses precision on floats, so we use toJSON instead. This isn't perfect outroSpace = if multiline then "\n${indent}" else " ";
# as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for in
# pretty-printing purposes this is acceptable. if isInt v then
else if isFloat v then builtins.toJSON v toString v
else if isString v then # toString loses precision on floats, so we use toJSON instead. This isn't perfect
let # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for
lines = filter (v: ! isList v) (split "\n" v); # pretty-printing purposes this is acceptable.
escapeSingleline = escape [ "\\" "\"" "\${" ]; else if isFloat v then
escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ]; builtins.toJSON v
singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\""; else if isString v then
multilineResult = let let
escapedLines = map escapeMultiline lines; lines = filter (v: !isList v) (split "\n" v);
# The last line gets a special treatment: if it's empty, '' is on its own line at the "outer" escapeSingleline = escape [
# indentation level. Otherwise, '' is appended to the last line. "\\"
lastLine = last escapedLines; "\""
in "''" + introSpace + concatStringsSep introSpace (init escapedLines) "\${"
+ (if lastLine == "" then outroSpace else introSpace + lastLine) + "''"; ];
in escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ];
if multiline && length lines > 1 then multilineResult else singlelineResult singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\"";
else if true == v then "true" multilineResult =
else if false == v then "false" let
else if null == v then "null" escapedLines = map escapeMultiline lines;
else if isPath v then toString v # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
else if isList v then # indentation level. Otherwise, '' is appended to the last line.
if v == [] then "[ ]" lastLine = last escapedLines;
else "[" + introSpace in
+ concatMapStringsSep introSpace (go (indent + " ")) v "''"
+ outroSpace + "]" + introSpace
else if isFunction v then + concatStringsSep introSpace (init escapedLines)
let fna = functionArgs v; + (if lastLine == "" then outroSpace else introSpace + lastLine)
showFnas = concatStringsSep ", " (mapAttrsToList + "''";
(name: hasDefVal: if hasDefVal then name + "?" else name) in
fna); if multiline && length lines > 1 then multilineResult else singlelineResult
in if fna == {} then "<function>" else if true == v then
else "<function, args: {${showFnas}}>" "true"
else if isAttrs v then else if false == v then
# apply pretty values if allowed "false"
if allowPrettyValues && v ? __pretty && v ? val else if null == v then
then v.__pretty v.val "null"
else if v == {} then "{ }" else if isPath v then
else if v ? type && v.type == "derivation" then toString v
"<derivation ${v.name or "???"}>" else if isList v then
else "{" + introSpace if v == [ ] then
+ concatStringsSep introSpace (mapAttrsToList "[ ]"
(name: value: else
"[" + introSpace + concatMapStringsSep introSpace (go (indent + " ")) v + outroSpace + "]"
else if isFunction v then
let
fna = functionArgs v;
showFnas = concatStringsSep ", " (
mapAttrsToList (name: hasDefVal: if hasDefVal then name + "?" else name) fna
);
in
if fna == { } then "<function>" else "<function, args: {${showFnas}}>"
else if isAttrs v then
# apply pretty values if allowed
if allowPrettyValues && v ? __pretty && v ? val then
v.__pretty v.val
else if v == { } then
"{ }"
else if v ? type && v.type == "derivation" then
"<derivation ${v.name or "???"}>"
else
"{"
+ introSpace
+ concatStringsSep introSpace (
mapAttrsToList (
name: value:
"${escapeNixIdentifier name} = ${ "${escapeNixIdentifier name} = ${
addErrorContext "while evaluating an attribute `${name}`" addErrorContext "while evaluating an attribute `${name}`" (go (indent + " ") value)
(go (indent + " ") value) };"
};") v) ) v
+ outroSpace + "}" )
else abort "generators.toPretty: should never happen (v = ${v})"; + outroSpace
in go indent; + "}"
else
abort "generators.toPretty: should never happen (v = ${v})";
in
go indent;
/** /**
Translate a simple Nix expression to [Plist notation](https://en.wikipedia.org/wiki/Property_list). Translate a simple Nix expression to [Plist notation](https://en.wikipedia.org/wiki/Property_list).
@ -554,56 +621,83 @@ in rec {
Value Value
: The value to be converted to Plist : The value to be converted to Plist
*/ */
toPlist = {}: v: let toPlist =
expr = ind: x: { }:
if x == null then "" else v:
if isBool x then bool ind x else let
if isInt x then int ind x else expr =
if isString x then str ind x else ind: x:
if isList x then list ind x else if x == null then
if isAttrs x then attrs ind x else ""
if isPath x then str ind (toString x) else else if isBool x then
if isFloat x then float ind x else bool ind x
abort "generators.toPlist: should never happen (v = ${v})"; else if isInt x then
int ind x
else if isString x then
str ind x
else if isList x then
list ind x
else if isAttrs x then
attrs ind x
else if isPath x then
str ind (toString x)
else if isFloat x then
float ind x
else
abort "generators.toPlist: should never happen (v = ${v})";
literal = ind: x: ind + x; literal = ind: x: ind + x;
bool = ind: x: literal ind (if x then "<true/>" else "<false/>"); bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
int = ind: x: literal ind "<integer>${toString x}</integer>"; int = ind: x: literal ind "<integer>${toString x}</integer>";
str = ind: x: literal ind "<string>${x}</string>"; str = ind: x: literal ind "<string>${x}</string>";
key = ind: x: literal ind "<key>${x}</key>"; key = ind: x: literal ind "<key>${x}</key>";
float = ind: x: literal ind "<real>${toString x}</real>"; float = ind: x: literal ind "<real>${toString x}</real>";
indent = ind: expr "\t${ind}"; indent = ind: expr "\t${ind}";
item = ind: concatMapStringsSep "\n" (indent ind); item = ind: concatMapStringsSep "\n" (indent ind);
list = ind: x: concatStringsSep "\n" [ list =
(literal ind "<array>") ind: x:
(item ind x) concatStringsSep "\n" [
(literal ind "</array>") (literal ind "<array>")
]; (item ind x)
(literal ind "</array>")
];
attrs = ind: x: concatStringsSep "\n" [ attrs =
(literal ind "<dict>") ind: x:
(attr ind x) concatStringsSep "\n" [
(literal ind "</dict>") (literal ind "<dict>")
]; (attr ind x)
(literal ind "</dict>")
];
attr = let attrFilter = name: value: name != "_module" && value != null; attr =
in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList let
(name: value: optionals (attrFilter name value) [ attrFilter = name: value: name != "_module" && value != null;
(key "\t${ind}" name) in
(expr "\t${ind}" value) ind: x:
]) x)); concatStringsSep "\n" (
flatten (
mapAttrsToList (
name: value:
optionals (attrFilter name value) [
(key "\t${ind}" name)
(expr "\t${ind}" value)
]
) x
)
);
in in
'' ''
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
${expr "" v} ${expr "" v}
</plist>''; </plist>'';
/** /**
Translate a simple Nix expression to Dhall notation. Translate a simple Nix expression to Dhall notation.
@ -621,13 +715,14 @@ in rec {
: The value to be converted to Dhall : The value to be converted to Dhall
*/ */
toDhall = { }@args: v: toDhall =
let concatItems = concatStringsSep ", "; { }@args:
in if isAttrs v then v:
"{ ${ let
concatItems (mapAttrsToList concatItems = concatStringsSep ", ";
(key: value: "${key} = ${toDhall args value}") v) in
} }" if isAttrs v then
"{ ${concatItems (mapAttrsToList (key: value: "${key} = ${toDhall args value}") v)} }"
else if isList v then else if isList v then
"[ ${concatItems (map (toDhall args) v)} ]" "[ ${concatItems (map (toDhall args) v)} ]"
else if isInt v then else if isInt v then
@ -655,7 +750,6 @@ in rec {
Regardless of multiline parameter there is no trailing newline. Regardless of multiline parameter there is no trailing newline.
# Inputs # Inputs
Structured function argument Structured function argument
@ -703,11 +797,13 @@ in rec {
::: :::
*/ */
toLua = { toLua =
multiline ? true, {
indent ? "", multiline ? true,
asBindings ? false, indent ? "",
}@args: v: asBindings ? false,
}@args:
v:
let let
innerIndent = "${indent} "; innerIndent = "${indent} ";
introSpace = if multiline then "\n${innerIndent}" else " "; introSpace = if multiline then "\n${innerIndent}" else " ";
@ -717,13 +813,16 @@ in rec {
asBindings = false; asBindings = false;
}; };
concatItems = concatStringsSep ",${introSpace}"; concatItems = concatStringsSep ",${introSpace}";
isLuaInline = { _type ? null, ... }: _type == "lua-inline"; isLuaInline =
{
_type ? null,
...
}:
_type == "lua-inline";
generatedBindings = generatedBindings =
assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}"; assert assertMsg (badVarNames == [ ]) "Bad Lua var names: ${toPretty { } badVarNames}";
concatStrings ( concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v);
mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v
);
# https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names # https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names
matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*"; matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*";
@ -736,8 +835,12 @@ in rec {
else if isInt v || isFloat v || isString v || isBool v then else if isInt v || isFloat v || isString v || isBool v then
toJSON v toJSON v
else if isList v then else if isList v then
(if v == [ ] then "{}" else (
"{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}") if v == [ ] then
"{}"
else
"{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}"
)
else if isAttrs v then else if isAttrs v then
( (
if isLuaInline v then if isLuaInline v then
@ -747,9 +850,9 @@ in rec {
else if isDerivation v then else if isDerivation v then
''"${toString v}"'' ''"${toString v}"''
else else
"{${introSpace}${concatItems ( "{${introSpace}${
mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v concatItems (mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v)
)}${outroSpace}}" }${outroSpace}}"
) )
else else
abort "generators.toLua: type ${typeOf v} is unsupported"; abort "generators.toLua: type ${typeOf v} is unsupported";
@ -757,7 +860,6 @@ in rec {
/** /**
Mark string as Lua expression to be inlined when processed by toLua. Mark string as Lua expression to be inlined when processed by toLua.
# Inputs # Inputs
`expr` `expr`
@ -770,8 +872,12 @@ in rec {
mkLuaInline :: String -> AttrSet mkLuaInline :: String -> AttrSet
``` ```
*/ */
mkLuaInline = expr: { _type = "lua-inline"; inherit expr; }; mkLuaInline = expr: {
} // { _type = "lua-inline";
inherit expr;
};
}
// {
/** /**
Generates JSON from an arbitrary (non-function) value. Generates JSON from an arbitrary (non-function) value.
For more information see the documentation of the builtin. For more information see the documentation of the builtin.
@ -786,7 +892,7 @@ in rec {
: The value to be converted to JSON : The value to be converted to JSON
*/ */
toJSON = {}: lib.strings.toJSON; toJSON = { }: lib.strings.toJSON;
/** /**
YAML has been a strict superset of JSON since 1.2, so we YAML has been a strict superset of JSON since 1.2, so we
@ -804,5 +910,5 @@ in rec {
: The value to be converted to YAML : The value to be converted to YAML
*/ */
toYAML = {}: lib.strings.toJSON; toYAML = { }: lib.strings.toJSON;
} }

File diff suppressed because it is too large Load diff

View file

@ -4,13 +4,30 @@
{ lib }: { lib }:
let let
inherit (lib.strings) toInt; inherit (lib.strings) toInt;
inherit (lib.trivial) compare min id warn pipe; inherit (lib.trivial)
compare
min
id
warn
pipe
;
inherit (lib.attrsets) mapAttrs; inherit (lib.attrsets) mapAttrs;
inherit (lib) max; inherit (lib) max;
in in
rec { rec {
inherit (builtins) head tail length isList elemAt concatLists filter elem genList map; inherit (builtins)
head
tail
length
isList
elemAt
concatLists
filter
elem
genList
map
;
/** /**
Create a list consisting of a single element. `singleton x` is Create a list consisting of a single element. `singleton x` is
@ -40,7 +57,7 @@ rec {
::: :::
*/ */
singleton = x: [x]; singleton = x: [ x ];
/** /**
Apply the function to each element in the list. Apply the function to each element in the list.
@ -82,7 +99,6 @@ rec {
`list` with `nul` as the starting value, i.e., `list` with `nul` as the starting value, i.e.,
`foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`. `foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`.
# Inputs # Inputs
`op` `op`
@ -119,14 +135,13 @@ rec {
::: :::
*/ */
foldr = op: nul: list: foldr =
op: nul: list:
let let
len = length list; len = length list;
fold' = n: fold' = n: if n == len then nul else op (elemAt list n) (fold' (n + 1));
if n == len in
then nul fold' 0;
else op (elemAt list n) (fold' (n + 1));
in fold' 0;
/** /**
`fold` is an alias of `foldr` for historic reasons `fold` is an alias of `foldr` for historic reasons
@ -134,7 +149,6 @@ rec {
# FIXME(Profpatsch): deprecate? # FIXME(Profpatsch): deprecate?
fold = foldr; fold = foldr;
/** /**
left fold, like `foldr`, but from the left: left fold, like `foldr`, but from the left:
@ -176,13 +190,12 @@ rec {
::: :::
*/ */
foldl = op: nul: list: foldl =
op: nul: list:
let let
foldl' = n: foldl' = n: if n == -1 then nul else op (foldl' (n - 1)) (elemAt list n);
if n == -1 in
then nul foldl' (length list - 1);
else op (foldl' (n - 1)) (elemAt list n);
in foldl' (length list - 1);
/** /**
Reduce a list by applying a binary operator from left to right, Reduce a list by applying a binary operator from left to right,
@ -261,13 +274,11 @@ rec {
::: :::
*/ */
foldl' = foldl' =
op: op: acc:
acc:
# The builtin `foldl'` is a bit lazier than one might expect. # The builtin `foldl'` is a bit lazier than one might expect.
# See https://github.com/NixOS/nix/pull/7158. # See https://github.com/NixOS/nix/pull/7158.
# In particular, the initial accumulator value is not forced before the first iteration starts. # In particular, the initial accumulator value is not forced before the first iteration starts.
builtins.seq acc builtins.seq acc (builtins.foldl' op acc);
(builtins.foldl' op acc);
/** /**
Map with index starting from 0 Map with index starting from 0
@ -304,7 +315,6 @@ rec {
/** /**
Map with index starting from 1 Map with index starting from 1
# Inputs # Inputs
`f` `f`
@ -374,12 +384,9 @@ rec {
::: :::
*/ */
ifilter0 = ifilter0 =
ipred: ipred: input:
input:
map (idx: elemAt input idx) ( map (idx: elemAt input idx) (
filter (idx: ipred idx (elemAt input idx)) ( filter (idx: ipred idx (elemAt input idx)) (genList (x: x) (length input))
genList (x: x) (length input)
)
); );
/** /**
@ -408,14 +415,12 @@ rec {
Flatten the argument into a single list; that is, nested lists are Flatten the argument into a single list; that is, nested lists are
spliced into the top-level lists. spliced into the top-level lists.
# Inputs # Inputs
`x` `x`
: 1\. Function argument : 1\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.flatten` usage example ## `lib.lists.flatten` usage example
@ -429,15 +434,11 @@ rec {
::: :::
*/ */
flatten = x: flatten = x: if isList x then concatMap (y: flatten y) x else [ x ];
if isList x
then concatMap (y: flatten y) x
else [x];
/** /**
Remove elements equal to 'e' from a list. Useful for buildInputs. Remove elements equal to 'e' from a list. Useful for buildInputs.
# Inputs # Inputs
`e` `e`
@ -465,8 +466,7 @@ rec {
::: :::
*/ */
remove = remove = e: filter (x: x != e);
e: filter (x: x != e);
/** /**
Find the sole element in the list matching the specified Find the sole element in the list matching the specified
@ -475,7 +475,6 @@ rec {
Returns `default` if no such element exists, or Returns `default` if no such element exists, or
`multiple` if there are multiple matching elements. `multiple` if there are multiple matching elements.
# Inputs # Inputs
`pred` `pred`
@ -516,14 +515,17 @@ rec {
::: :::
*/ */
findSingle = findSingle =
pred: pred: default: multiple: list:
default: let
multiple: found = filter pred list;
list: len = length found;
let found = filter pred list; len = length found; in
in if len == 0 then default if len == 0 then
else if len != 1 then multiple default
else head found; else if len != 1 then
multiple
else
head found;
/** /**
Find the first index in the list matching the specified Find the first index in the list matching the specified
@ -563,9 +565,7 @@ rec {
::: :::
*/ */
findFirstIndex = findFirstIndex =
pred: pred: default: list:
default:
list:
let let
# A naive recursive implementation would be much simpler, but # A naive recursive implementation would be much simpler, but
# would also overflow the evaluator stack. We use `foldl'` as a workaround # would also overflow the evaluator stack. We use `foldl'` as a workaround
@ -580,12 +580,13 @@ rec {
# - if index >= 0 then pred (elemAt list index) and all elements before (elemAt list index) didn't satisfy pred # - if index >= 0 then pred (elemAt list index) and all elements before (elemAt list index) didn't satisfy pred
# #
# We start with index -1 and the 0'th element of the list, which satisfies the invariant # We start with index -1 and the 0'th element of the list, which satisfies the invariant
resultIndex = foldl' (index: el: resultIndex = foldl' (
index: el:
if index < 0 then if index < 0 then
# No match yet before the current index, we need to check the element # No match yet before the current index, we need to check the element
if pred el then if pred el then
# We have a match! Turn it into the actual index to prevent future iterations from modifying it # We have a match! Turn it into the actual index to prevent future iterations from modifying it
- index - 1 -index - 1
else else
# Still no match, update the index to the next element (we're counting down, so minus one) # Still no match, update the index to the next element (we're counting down, so minus one)
index - 1 index - 1
@ -594,10 +595,7 @@ rec {
index index
) (-1) list; ) (-1) list;
in in
if resultIndex < 0 then if resultIndex < 0 then default else resultIndex;
default
else
resultIndex;
/** /**
Find the first element in the list matching the specified Find the first element in the list matching the specified
@ -637,16 +635,11 @@ rec {
::: :::
*/ */
findFirst = findFirst =
pred: pred: default: list:
default:
list:
let let
index = findFirstIndex pred null list; index = findFirstIndex pred null list;
in in
if index == null then if index == null then default else elemAt list index;
default
else
elemAt list index;
/** /**
Return true if function `pred` returns true for at least one Return true if function `pred` returns true for at least one
@ -745,8 +738,7 @@ rec {
::: :::
*/ */
count = count = pred: foldl' (c: x: if pred x then c + 1 else c) 0;
pred: foldl' (c: x: if pred x then c + 1 else c) 0;
/** /**
Return a singleton list or an empty list, depending on a boolean Return a singleton list or an empty list, depending on a boolean
@ -782,7 +774,7 @@ rec {
::: :::
*/ */
optional = cond: elem: if cond then [elem] else []; optional = cond: elem: if cond then [ elem ] else [ ];
/** /**
Return a list or an empty list, depending on a boolean value. Return a list or an empty list, depending on a boolean value.
@ -816,10 +808,7 @@ rec {
::: :::
*/ */
optionals = optionals = cond: elems: if cond then elems else [ ];
cond:
elems: if cond then elems else [];
/** /**
If argument is a list, return it; else, wrap it in a singleton If argument is a list, return it; else, wrap it in a singleton
@ -845,7 +834,7 @@ rec {
::: :::
*/ */
toList = x: if isList x then x else [x]; toList = x: if isList x then x else [ x ];
/** /**
Return a list of integers from `first` up to and including `last`. Return a list of integers from `first` up to and including `last`.
@ -879,13 +868,7 @@ rec {
::: :::
*/ */
range = range = first: last: if first > last then [ ] else genList (n: first + n) (last - first + 1);
first:
last:
if first > last then
[]
else
genList (n: first + n) (last - first + 1);
/** /**
Return a list with `n` copies of an element. Return a list with `n` copies of an element.
@ -977,7 +960,6 @@ rec {
: 4\. Function argument : 4\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.groupBy'` usage example ## `lib.lists.groupBy'` usage example
@ -1002,15 +984,21 @@ rec {
::: :::
*/ */
groupBy' = op: nul: pred: lst: mapAttrs (name: foldl op nul) (groupBy pred lst); groupBy' =
op: nul: pred: lst:
mapAttrs (name: foldl op nul) (groupBy pred lst);
groupBy = builtins.groupBy or ( groupBy =
pred: foldl' (r: e: builtins.groupBy or (
let pred:
key = pred e; foldl' (
in r: e:
r // { ${key} = (r.${key} or []) ++ [e]; } let
) {}); key = pred e;
in
r // { ${key} = (r.${key} or [ ]) ++ [ e ]; }
) { }
);
/** /**
Merges two lists of the same size together. If the sizes aren't the same Merges two lists of the same size together. If the sizes aren't the same
@ -1049,11 +1037,8 @@ rec {
::: :::
*/ */
zipListsWith = zipListsWith =
f: f: fst: snd:
fst: genList (n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd));
snd:
genList
(n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd));
/** /**
Merges two lists of the same size together. If the sizes aren't the same Merges two lists of the same size together. If the sizes aren't the same
@ -1114,8 +1099,12 @@ rec {
::: :::
*/ */
reverseList = xs: reverseList =
let l = length xs; in genList (n: elemAt xs (l - n - 1)) l; xs:
let
l = length xs;
in
genList (n: elemAt xs (l - n - 1)) l;
/** /**
Depth-First Search (DFS) for lists `list != []`. Depth-First Search (DFS) for lists `list != []`.
@ -1123,7 +1112,6 @@ rec {
`before a b == true` means that `b` depends on `a` (there's an `before a b == true` means that `b` depends on `a` (there's an
edge from `b` to `a`). edge from `b` to `a`).
# Inputs # Inputs
`stopOnCycles` `stopOnCycles`
@ -1138,7 +1126,6 @@ rec {
: 3\. Function argument : 3\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.listDfs` usage example ## `lib.lists.listDfs` usage example
@ -1159,22 +1146,32 @@ rec {
::: :::
*/ */
listDfs = stopOnCycles: before: list: listDfs =
stopOnCycles: before: list:
let let
dfs' = us: visited: rest: dfs' =
us: visited: rest:
let let
c = filter (x: before x us) visited; c = filter (x: before x us) visited;
b = partition (x: before x us) rest; b = partition (x: before x us) rest;
in if stopOnCycles && (length c > 0) in
then { cycle = us; loops = c; inherit visited rest; } if stopOnCycles && (length c > 0) then
else if length b.right == 0 {
then # nothing is before us cycle = us;
{ minimal = us; inherit visited rest; } loops = c;
else # grab the first one before us and continue inherit visited rest;
dfs' (head b.right) }
([ us ] ++ visited) else if length b.right == 0 then
(tail b.right ++ b.wrong); # nothing is before us
in dfs' (head list) [] (tail list); {
minimal = us;
inherit visited rest;
}
else
# grab the first one before us and continue
dfs' (head b.right) ([ us ] ++ visited) (tail b.right ++ b.wrong);
in
dfs' (head list) [ ] (tail list);
/** /**
Sort a list based on a partial ordering using DFS. This Sort a list based on a partial ordering using DFS. This
@ -1184,7 +1181,6 @@ rec {
`before a b == true` means that `b` should be after `a` `before a b == true` means that `b` should be after `a`
in the result. in the result.
# Inputs # Inputs
`before` `before`
@ -1195,7 +1191,6 @@ rec {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.toposort` usage example ## `lib.lists.toposort` usage example
@ -1216,24 +1211,28 @@ rec {
::: :::
*/ */
toposort = before: list: toposort =
before: list:
let let
dfsthis = listDfs true before list; dfsthis = listDfs true before list;
toporest = toposort before (dfsthis.visited ++ dfsthis.rest); toporest = toposort before (dfsthis.visited ++ dfsthis.rest);
in in
if length list < 2 if length list < 2 then
then # finish # finish
{ result = list; } { result = list; }
else if dfsthis ? cycle else if dfsthis ? cycle then
then # there's a cycle, starting from the current vertex, return it # there's a cycle, starting from the current vertex, return it
{ cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited); {
inherit (dfsthis) loops; } cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited);
else if toporest ? cycle inherit (dfsthis) loops;
then # there's a cycle somewhere else in the graph, return it }
toporest else if toporest ? cycle then
# Slow, but short. Can be made a bit faster with an explicit stack. # there's a cycle somewhere else in the graph, return it
else # there are no cycles toporest
{ result = [ dfsthis.minimal ] ++ toporest.result; }; # Slow, but short. Can be made a bit faster with an explicit stack.
else
# there are no cycles
{ result = [ dfsthis.minimal ] ++ toporest.result; };
/** /**
Sort a list based on a comparator function which compares two Sort a list based on a comparator function which compares two
@ -1289,7 +1288,6 @@ rec {
sortOn f == sort (p: q: f p < f q) sortOn f == sort (p: q: f p < f q)
``` ```
# Inputs # Inputs
`f` `f`
@ -1317,18 +1315,22 @@ rec {
::: :::
*/ */
sortOn = f: list: sortOn =
f: list:
let let
# Heterogenous list as pair may be ugly, but requires minimal allocations. # Heterogenous list as pair may be ugly, but requires minimal allocations.
pairs = map (x: [(f x) x]) list; pairs = map (x: [
(f x)
x
]) list;
in in
map map (x: builtins.elemAt x 1) (
(x: builtins.elemAt x 1) sort
(sort # Compare the first element of the pairs
# Compare the first element of the pairs # Do not factor out the `<`, to avoid calls in hot code; duplicate instead.
# Do not factor out the `<`, to avoid calls in hot code; duplicate instead. (a: b: head a < head b)
(a: b: head a < head b) pairs
pairs); );
/** /**
Compare two lists element-by-element. Compare two lists element-by-element.
@ -1347,7 +1349,6 @@ rec {
: 3\. Function argument : 3\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.compareLists` usage example ## `lib.lists.compareLists` usage example
@ -1365,30 +1366,28 @@ rec {
::: :::
*/ */
compareLists = cmp: a: b: compareLists =
if a == [] cmp: a: b:
then if b == [] if a == [ ] then
then 0 if b == [ ] then 0 else -1
else -1 else if b == [ ] then
else if b == [] 1
then 1 else
else let rel = cmp (head a) (head b); in let
if rel == 0 rel = cmp (head a) (head b);
then compareLists cmp (tail a) (tail b) in
else rel; if rel == 0 then compareLists cmp (tail a) (tail b) else rel;
/** /**
Sort list using "Natural sorting". Sort list using "Natural sorting".
Numeric portions of strings are sorted in numeric order. Numeric portions of strings are sorted in numeric order.
# Inputs # Inputs
`lst` `lst`
: 1\. Function argument : 1\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.naturalSort` usage example ## `lib.lists.naturalSort` usage example
@ -1404,18 +1403,21 @@ rec {
::: :::
*/ */
naturalSort = lst: naturalSort =
lst:
let let
vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s); vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s);
prepared = map (x: [ (vectorise x) x ]) lst; # remember vectorised version for O(n) regex splits prepared = map (x: [
(vectorise x)
x
]) lst; # remember vectorised version for O(n) regex splits
less = a: b: (compareLists compare (head a) (head b)) < 0; less = a: b: (compareLists compare (head a) (head b)) < 0;
in in
map (x: elemAt x 1) (sort less prepared); map (x: elemAt x 1) (sort less prepared);
/** /**
Return the first (at most) N elements of a list. Return the first (at most) N elements of a list.
# Inputs # Inputs
`count` `count`
@ -1445,13 +1447,11 @@ rec {
::: :::
*/ */
take = take = count: sublist 0 count;
count: sublist 0 count;
/** /**
Remove the first (at most) N elements of a list. Remove the first (at most) N elements of a list.
# Inputs # Inputs
`count` `count`
@ -1481,14 +1481,11 @@ rec {
::: :::
*/ */
drop = drop = count: list: sublist count (length list) list;
count:
list: sublist count (length list) list;
/** /**
Remove the last (at most) N elements of a list. Remove the last (at most) N elements of a list.
# Inputs # Inputs
`count` `count`
@ -1517,18 +1514,12 @@ rec {
=> [ ] => [ ]
``` ```
::: :::
*/
*/ dropEnd = n: xs: take (max 0 (length xs - n)) xs;
dropEnd =
n: xs:
take
(max 0 (length xs - n))
xs;
/** /**
Whether the first list is a prefix of the second list. Whether the first list is a prefix of the second list.
# Inputs # Inputs
`list1` `list1`
@ -1558,10 +1549,7 @@ rec {
::: :::
*/ */
hasPrefix = hasPrefix = list1: list2: take (length list1) list2 == list1;
list1:
list2:
take (length list1) list2 == list1;
/** /**
Remove the first list as a prefix from the second list. Remove the first list as a prefix from the second list.
@ -1597,8 +1585,7 @@ rec {
::: :::
*/ */
removePrefix = removePrefix =
list1: list1: list2:
list2:
if hasPrefix list1 list2 then if hasPrefix list1 list2 then
drop (length list1) list2 drop (length list1) list2
else else
@ -1642,20 +1629,22 @@ rec {
::: :::
*/ */
sublist = sublist =
start: start: count: list:
count: let
list: len = length list;
let len = length list; in in
genList genList (n: elemAt list (n + start)) (
(n: elemAt list (n + start)) if start >= len then
(if start >= len then 0 0
else if start + count > len then len - start else if start + count > len then
else count); len - start
else
count
);
/** /**
The common prefix of two lists. The common prefix of two lists.
# Inputs # Inputs
`list1` `list1`
@ -1688,8 +1677,7 @@ rec {
::: :::
*/ */
commonPrefix = commonPrefix =
list1: list1: list2:
list2:
let let
# Zip the lists together into a list of booleans whether each element matches # Zip the lists together into a list of booleans whether each element matches
matchings = zipListsWith (fst: snd: fst != snd) list1 list2; matchings = zipListsWith (fst: snd: fst != snd) list1 list2;
@ -1706,7 +1694,6 @@ rec {
This function throws an error if the list is empty. This function throws an error if the list is empty.
# Inputs # Inputs
`list` `list`
@ -1730,8 +1717,9 @@ rec {
::: :::
*/ */
last = list: last =
assert lib.assertMsg (list != []) "lists.last: list must not be empty!"; list:
assert lib.assertMsg (list != [ ]) "lists.last: list must not be empty!";
elemAt list (length list - 1); elemAt list (length list - 1);
/** /**
@ -1739,7 +1727,6 @@ rec {
This function throws an error if the list is empty. This function throws an error if the list is empty.
# Inputs # Inputs
`list` `list`
@ -1763,15 +1750,14 @@ rec {
::: :::
*/ */
init = list: init =
assert lib.assertMsg (list != []) "lists.init: list must not be empty!"; list:
assert lib.assertMsg (list != [ ]) "lists.init: list must not be empty!";
take (length list - 1) list; take (length list - 1) list;
/** /**
Return the image of the cross product of some lists by a function. Return the image of the cross product of some lists by a function.
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.crossLists` usage example ## `lib.lists.crossLists` usage example
@ -1801,13 +1787,11 @@ rec {
nix-repl> lib.mapCartesianProduct ({x,y}: x+y) { x = [1 2]; y = [3 4]; } nix-repl> lib.mapCartesianProduct ({x,y}: x+y) { x = [1 2]; y = [3 4]; }
[ 4 5 5 6 ] [ 4 5 5 6 ]
'' '' (f: foldl (fs: args: concatMap (f: map f args) fs) [ f ]);
(f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
/** /**
Remove duplicate elements from the `list`. O(n^2) complexity. Remove duplicate elements from the `list`. O(n^2) complexity.
# Inputs # Inputs
`list` `list`
@ -1831,12 +1815,11 @@ rec {
::: :::
*/ */
unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) []; unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) [ ];
/** /**
Check if list contains only unique elements. O(n^2) complexity. Check if list contains only unique elements. O(n^2) complexity.
# Inputs # Inputs
`list` `list`
@ -1864,7 +1847,6 @@ rec {
*/ */
allUnique = list: (length (unique list) == length list); allUnique = list: (length (unique list) == length list);
/** /**
Intersects list 'list1' and another list (`list2`). Intersects list 'list1' and another list (`list2`).
@ -1880,7 +1862,6 @@ rec {
: Second list : Second list
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.intersectLists` usage example ## `lib.lists.intersectLists` usage example
@ -1909,7 +1890,6 @@ rec {
: Second list : Second list
# Examples # Examples
:::{.example} :::{.example}
## `lib.lists.subtractLists` usage example ## `lib.lists.subtractLists` usage example

View file

@ -6,14 +6,20 @@
{ lib }: { lib }:
let let
inherit (lib) matchAttrs any all isDerivation getBin assertMsg; inherit (lib)
matchAttrs
any
all
isDerivation
getBin
assertMsg
;
inherit (lib.attrsets) mapAttrs' filterAttrs; inherit (lib.attrsets) mapAttrs' filterAttrs;
inherit (builtins) isString match typeOf; inherit (builtins) isString match typeOf;
in in
rec { rec {
/** /**
Add to or override the meta attributes of the given Add to or override the meta attributes of the given
derivation. derivation.
@ -28,7 +34,6 @@ rec {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.meta.addMetaAttrs` usage example ## `lib.meta.addMetaAttrs` usage example
@ -39,9 +44,7 @@ rec {
::: :::
*/ */
addMetaAttrs = newAttrs: drv: addMetaAttrs = newAttrs: drv: drv // { meta = (drv.meta or { }) // newAttrs; };
drv // { meta = (drv.meta or {}) // newAttrs; };
/** /**
Disable Hydra builds of given derivation. Disable Hydra builds of given derivation.
@ -52,8 +55,7 @@ rec {
: 1\. Function argument : 1\. Function argument
*/ */
dontDistribute = drv: addMetaAttrs { hydraPlatforms = []; } drv; dontDistribute = drv: addMetaAttrs { hydraPlatforms = [ ]; } drv;
/** /**
Change the [symbolic name of a derivation](https://nixos.org/manual/nix/stable/language/derivations.html#attr-name). Change the [symbolic name of a derivation](https://nixos.org/manual/nix/stable/language/derivations.html#attr-name).
@ -72,8 +74,7 @@ rec {
: 2\. Function argument : 2\. Function argument
*/ */
setName = name: drv: drv // {inherit name;}; setName = name: drv: drv // { inherit name; };
/** /**
Like `setName`, but takes the previous name as an argument. Like `setName`, but takes the previous name as an argument.
@ -88,7 +89,6 @@ rec {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.meta.updateName` usage example ## `lib.meta.updateName` usage example
@ -99,8 +99,7 @@ rec {
::: :::
*/ */
updateName = updater: drv: drv // {name = updater (drv.name);}; updateName = updater: drv: drv // { name = updater (drv.name); };
/** /**
Append a suffix to the name of a package (before the version Append a suffix to the name of a package (before the version
@ -112,14 +111,19 @@ rec {
: 1\. Function argument : 1\. Function argument
*/ */
appendToName = suffix: updateName (name: appendToName =
let x = builtins.parseDrvName name; in "${x.name}-${suffix}-${x.version}"); suffix:
updateName (
name:
let
x = builtins.parseDrvName name;
in
"${x.name}-${suffix}-${x.version}"
);
/** /**
Apply a function to each derivation and only to derivations in an attrset. Apply a function to each derivation and only to derivations in an attrset.
# Inputs # Inputs
`f` `f`
@ -130,11 +134,12 @@ rec {
: 2\. Function argument : 2\. Function argument
*/ */
mapDerivationAttrset = f: set: lib.mapAttrs (name: pkg: if lib.isDerivation pkg then (f pkg) else pkg) set; mapDerivationAttrset =
f: set: lib.mapAttrs (name: pkg: if lib.isDerivation pkg then (f pkg) else pkg) set;
/** /**
The default priority of packages in Nix. See `defaultPriority` in [`src/nix/profile.cc`](https://github.com/NixOS/nix/blob/master/src/nix/profile.cc#L47). The default priority of packages in Nix. See `defaultPriority` in [`src/nix/profile.cc`](https://github.com/NixOS/nix/blob/master/src/nix/profile.cc#L47).
*/ */
defaultPriority = 5; defaultPriority = 5;
/** /**
@ -159,7 +164,6 @@ rec {
`drv` `drv`
: 1\. Function argument : 1\. Function argument
*/ */
lowPrio = setPrio 10; lowPrio = setPrio 10;
@ -174,7 +178,6 @@ rec {
*/ */
lowPrioSet = set: mapDerivationAttrset lowPrio set; lowPrioSet = set: mapDerivationAttrset lowPrio set;
/** /**
Increase the nix-env priority of the package, i.e., this Increase the nix-env priority of the package, i.e., this
version/variant of the package will be preferred. version/variant of the package will be preferred.
@ -198,7 +201,6 @@ rec {
*/ */
hiPrioSet = set: mapDerivationAttrset hiPrio set; hiPrioSet = set: mapDerivationAttrset hiPrio set;
/** /**
Check to see if a platform is matched by the given `meta.platforms` Check to see if a platform is matched by the given `meta.platforms`
element. element.
@ -214,7 +216,6 @@ rec {
We can inject these into a pattern for the whole of a structured platform, We can inject these into a pattern for the whole of a structured platform,
and then match that. and then match that.
# Inputs # Inputs
`platform` `platform`
@ -225,7 +226,6 @@ rec {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.meta.platformMatch` usage example ## `lib.meta.platformMatch` usage example
@ -237,21 +237,24 @@ rec {
::: :::
*/ */
platformMatch = platform: elem: ( platformMatch =
# Check with simple string comparison if elem was a string. platform: elem:
# (
# The majority of comparisons done with this function will be against meta.platforms # Check with simple string comparison if elem was a string.
# which contains a simple platform string. #
# # The majority of comparisons done with this function will be against meta.platforms
# Avoiding an attrset allocation results in significant performance gains (~2-30) across the board in OfBorg # which contains a simple platform string.
# because this is a hot path for nixpkgs. #
if isString elem then platform ? system && elem == platform.system # Avoiding an attrset allocation results in significant performance gains (~2-30) across the board in OfBorg
else matchAttrs ( # because this is a hot path for nixpkgs.
# Normalize platform attrset. if isString elem then
if elem ? parsed then elem platform ? system && elem == platform.system
else { parsed = elem; } else
) platform matchAttrs (
); # Normalize platform attrset.
if elem ? parsed then elem else { parsed = elem; }
) platform
);
/** /**
Check if a package is available on a given platform. Check if a package is available on a given platform.
@ -263,7 +266,6 @@ rec {
2. None of `meta.badPlatforms` pattern matches the given platform. 2. None of `meta.badPlatforms` pattern matches the given platform.
# Inputs # Inputs
`platform` `platform`
@ -274,7 +276,6 @@ rec {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.meta.availableOn` usage example ## `lib.meta.availableOn` usage example
@ -286,9 +287,10 @@ rec {
::: :::
*/ */
availableOn = platform: pkg: availableOn =
((!pkg?meta.platforms) || any (platformMatch platform) pkg.meta.platforms) && platform: pkg:
all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []); ((!pkg ? meta.platforms) || any (platformMatch platform) pkg.meta.platforms)
&& all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or [ ]);
/** /**
Mapping of SPDX ID to the attributes in lib.licenses. Mapping of SPDX ID to the attributes in lib.licenses.
@ -309,13 +311,10 @@ rec {
::: :::
*/ */
licensesSpdx = licensesSpdx = mapAttrs' (_key: license: {
mapAttrs' name = license.spdxId;
(_key: license: { value = license;
name = license.spdxId; }) (filterAttrs (_key: license: license ? spdxId) lib.licenses);
value = license;
})
(filterAttrs (_key: license: license ? spdxId) lib.licenses);
/** /**
Get the corresponding attribute in lib.licenses from the SPDX ID Get the corresponding attribute in lib.licenses from the SPDX ID
@ -348,10 +347,11 @@ rec {
*/ */
getLicenseFromSpdxId = getLicenseFromSpdxId =
licstr: licstr:
getLicenseFromSpdxIdOr licstr ( getLicenseFromSpdxIdOr licstr (
lib.warn "getLicenseFromSpdxId: No license matches the given SPDX ID: ${licstr}" lib.warn "getLicenseFromSpdxId: No license matches the given SPDX ID: ${licstr}" {
{ shortName = licstr; } shortName = licstr;
); }
);
/** /**
Get the corresponding attribute in lib.licenses from the SPDX ID Get the corresponding attribute in lib.licenses from the SPDX ID
@ -398,13 +398,12 @@ rec {
name = lib.toLower name; name = lib.toLower name;
inherit value; inherit value;
}) licensesSpdx; }) licensesSpdx;
in licstr: default: in
lowercaseLicenses.${ lib.toLower licstr } or default; licstr: default: lowercaseLicenses.${lib.toLower licstr} or default;
/** /**
Get the path to the main program of a package based on meta.mainProgram Get the path to the main program of a package based on meta.mainProgram
# Inputs # Inputs
`x` `x`
@ -430,17 +429,23 @@ rec {
::: :::
*/ */
getExe = x: getExe' x (x.meta.mainProgram or ( getExe =
# This could be turned into an error when 23.05 is at end of life x:
lib.warn "getExe: Package ${lib.strings.escapeNixIdentifier x.meta.name or x.pname or x.name} does not have the meta.mainProgram attribute. We'll assume that the main program has the same name for now, but this behavior is deprecated, because it leads to surprising errors when the assumption does not hold. If the package has a main program, please set `meta.mainProgram` in its definition to make this warning go away. Otherwise, if the package does not have a main program, or if you don't control its definition, use getExe' to specify the name to the program, such as lib.getExe' foo \"bar\"." getExe' x (
lib.getName x.meta.mainProgram or (
x # This could be turned into an error when 23.05 is at end of life
)); lib.warn
"getExe: Package ${
lib.strings.escapeNixIdentifier x.meta.name or x.pname or x.name
} does not have the meta.mainProgram attribute. We'll assume that the main program has the same name for now, but this behavior is deprecated, because it leads to surprising errors when the assumption does not hold. If the package has a main program, please set `meta.mainProgram` in its definition to make this warning go away. Otherwise, if the package does not have a main program, or if you don't control its definition, use getExe' to specify the name to the program, such as lib.getExe' foo \"bar\"."
lib.getName
x
)
);
/** /**
Get the path of a program of a derivation. Get the path of a program of a derivation.
# Inputs # Inputs
`x` `x`
@ -470,7 +475,8 @@ rec {
::: :::
*/ */
getExe' = x: y: getExe' =
x: y:
assert assertMsg (isDerivation x) assert assertMsg (isDerivation x)
"lib.meta.getExe': The first argument is of type ${typeOf x}, but it should be a derivation instead."; "lib.meta.getExe': The first argument is of type ${typeOf x}, but it should be a derivation instead.";
assert assertMsg (isString y) assert assertMsg (isString y)

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
/* Nixpkgs/NixOS option handling. */ # Nixpkgs/NixOS option handling.
{ lib }: { lib }:
let let
@ -40,426 +40,520 @@ let
last last
; ;
prioritySuggestion = '' prioritySuggestion = ''
Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions. Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions.
''; '';
in in
rec { rec {
/* Returns true when the given argument is an option /*
Returns true when the given argument is an option
Type: isOption :: a -> bool Type: isOption :: a -> bool
Example: Example:
isOption 1 // => false isOption 1 // => false
isOption (mkOption {}) // => true isOption (mkOption {}) // => true
*/ */
isOption = lib.isType "option"; isOption = lib.isType "option";
/* Creates an Option attribute set. mkOption accepts an attribute set with the following keys: /*
Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
All keys default to `null` when not given. All keys default to `null` when not given.
Example: Example:
mkOption { } // => { _type = "option"; } mkOption { } // => { _type = "option"; }
mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; } mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; }
*/ */
mkOption = mkOption =
{ {
# Default value used when no definition is given in the configuration. # Default value used when no definition is given in the configuration.
default ? null, default ? null,
# Textual representation of the default, for the manual. # Textual representation of the default, for the manual.
defaultText ? null, defaultText ? null,
# Example value used in the manual. # Example value used in the manual.
example ? null, example ? null,
# String describing the option. # String describing the option.
description ? null, description ? null,
# Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix). # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix).
relatedPackages ? null, relatedPackages ? null,
# Option type, providing type-checking and value merging. # Option type, providing type-checking and value merging.
type ? null, type ? null,
# Function that converts the option value to something else. # Function that converts the option value to something else.
apply ? null, apply ? null,
# Whether the option is for NixOS developers only. # Whether the option is for NixOS developers only.
internal ? null, internal ? null,
# Whether the option shows up in the manual. Default: true. Use false to hide the option and any sub-options from submodules. Use "shallow" to hide only sub-options. # Whether the option shows up in the manual. Default: true. Use false to hide the option and any sub-options from submodules. Use "shallow" to hide only sub-options.
visible ? null, visible ? null,
# Whether the option can be set only once # Whether the option can be set only once
readOnly ? null, readOnly ? null,
} @ attrs: }@attrs:
attrs // { _type = "option"; }; attrs // { _type = "option"; };
/* Creates an Option attribute set for a boolean value option i.e an /*
option to be toggled on or off: Creates an Option attribute set for a boolean value option i.e an
option to be toggled on or off:
Example: Example:
mkEnableOption "foo" mkEnableOption "foo"
=> { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; } => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; }
*/ */
mkEnableOption = mkEnableOption =
# Name for the created option # Name for the created option
name: mkOption { name:
default = false; mkOption {
example = true; default = false;
description = "Whether to enable ${name}."; example = true;
type = lib.types.bool; description = "Whether to enable ${name}.";
}; type = lib.types.bool;
};
/* Creates an Option attribute set for an option that specifies the /*
package a module should use for some purpose. Creates an Option attribute set for an option that specifies the
package a module should use for some purpose.
The package is specified in the third argument under `default` as a list of strings The package is specified in the third argument under `default` as a list of strings
representing its attribute path in nixpkgs (or another package set). representing its attribute path in nixpkgs (or another package set).
Because of this, you need to pass nixpkgs itself (usually `pkgs` in a module; Because of this, you need to pass nixpkgs itself (usually `pkgs` in a module;
alternatively to nixpkgs itself, another package set) as the first argument. alternatively to nixpkgs itself, another package set) as the first argument.
If you pass another package set you should set the `pkgsText` option. If you pass another package set you should set the `pkgsText` option.
This option is used to display the expression for the package set. It is `"pkgs"` by default. This option is used to display the expression for the package set. It is `"pkgs"` by default.
If your expression is complex you should parenthesize it, as the `pkgsText` argument If your expression is complex you should parenthesize it, as the `pkgsText` argument
is usually immediately followed by an attribute lookup (`.`). is usually immediately followed by an attribute lookup (`.`).
The second argument may be either a string or a list of strings. The second argument may be either a string or a list of strings.
It provides the display name of the package in the description of the generated option It provides the display name of the package in the description of the generated option
(using only the last element if the passed value is a list) (using only the last element if the passed value is a list)
and serves as the fallback value for the `default` argument. and serves as the fallback value for the `default` argument.
To include extra information in the description, pass `extraDescription` to To include extra information in the description, pass `extraDescription` to
append arbitrary text to the generated description. append arbitrary text to the generated description.
You can also pass an `example` value, either a literal string or an attribute path. You can also pass an `example` value, either a literal string or an attribute path.
The `default` argument can be omitted if the provided name is The `default` argument can be omitted if the provided name is
an attribute of pkgs (if `name` is a string) or a valid attribute path in pkgs (if `name` is a list). an attribute of pkgs (if `name` is a string) or a valid attribute path in pkgs (if `name` is a list).
You can also set `default` to just a string in which case it is interpreted as an attribute name You can also set `default` to just a string in which case it is interpreted as an attribute name
(a singleton attribute path, if you will). (a singleton attribute path, if you will).
If you wish to explicitly provide no default, pass `null` as `default`. If you wish to explicitly provide no default, pass `null` as `default`.
If you want users to be able to set no package, pass `nullable = true`. If you want users to be able to set no package, pass `nullable = true`.
In this mode a `default = null` will not be interpreted as no default and is interpreted literally. In this mode a `default = null` will not be interpreted as no default and is interpreted literally.
Type: mkPackageOption :: pkgs -> (string|[string]) -> { nullable? :: bool, default? :: string|[string], example? :: null|string|[string], extraDescription? :: string, pkgsText? :: string } -> option Type: mkPackageOption :: pkgs -> (string|[string]) -> { nullable? :: bool, default? :: string|[string], example? :: null|string|[string], extraDescription? :: string, pkgsText? :: string } -> option
Example: Example:
mkPackageOption pkgs "hello" { } mkPackageOption pkgs "hello" { }
=> { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; } => { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; }
Example: Example:
mkPackageOption pkgs "GHC" { mkPackageOption pkgs "GHC" {
default = [ "ghc" ]; default = [ "ghc" ];
example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])";
} }
=> { ...; default = pkgs.ghc; defaultText = literalExpression "pkgs.ghc"; description = "The GHC package to use."; example = literalExpression "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; type = package; } => { ...; default = pkgs.ghc; defaultText = literalExpression "pkgs.ghc"; description = "The GHC package to use."; example = literalExpression "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; type = package; }
Example: Example:
mkPackageOption pkgs [ "python3Packages" "pytorch" ] { mkPackageOption pkgs [ "python3Packages" "pytorch" ] {
extraDescription = "This is an example and doesn't actually do anything."; extraDescription = "This is an example and doesn't actually do anything.";
} }
=> { ...; default = pkgs.python3Packages.pytorch; defaultText = literalExpression "pkgs.python3Packages.pytorch"; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = package; } => { ...; default = pkgs.python3Packages.pytorch; defaultText = literalExpression "pkgs.python3Packages.pytorch"; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = package; }
Example: Example:
mkPackageOption pkgs "nushell" { mkPackageOption pkgs "nushell" {
nullable = true; nullable = true;
} }
=> { ...; default = pkgs.nushell; defaultText = literalExpression "pkgs.nushell"; description = "The nushell package to use."; type = nullOr package; } => { ...; default = pkgs.nushell; defaultText = literalExpression "pkgs.nushell"; description = "The nushell package to use."; type = nullOr package; }
Example: Example:
mkPackageOption pkgs "coreutils" { mkPackageOption pkgs "coreutils" {
default = null; default = null;
} }
=> { ...; description = "The coreutils package to use."; type = package; } => { ...; description = "The coreutils package to use."; type = package; }
Example: Example:
mkPackageOption pkgs "dbus" { mkPackageOption pkgs "dbus" {
nullable = true; nullable = true;
default = null; default = null;
} }
=> { ...; default = null; description = "The dbus package to use."; type = nullOr package; } => { ...; default = null; description = "The dbus package to use."; type = nullOr package; }
Example: Example:
mkPackageOption pkgs.javaPackages "OpenJFX" { mkPackageOption pkgs.javaPackages "OpenJFX" {
default = "openjfx20"; default = "openjfx20";
pkgsText = "pkgs.javaPackages"; pkgsText = "pkgs.javaPackages";
} }
=> { ...; default = pkgs.javaPackages.openjfx20; defaultText = literalExpression "pkgs.javaPackages.openjfx20"; description = "The OpenJFX package to use."; type = package; } => { ...; default = pkgs.javaPackages.openjfx20; defaultText = literalExpression "pkgs.javaPackages.openjfx20"; description = "The OpenJFX package to use."; type = package; }
*/ */
mkPackageOption = mkPackageOption =
# Package set (an instantiation of nixpkgs such as pkgs in modules or another package set) # Package set (an instantiation of nixpkgs such as pkgs in modules or another package set)
pkgs: pkgs:
# Name for the package, shown in option description # Name for the package, shown in option description
name: name:
{ {
# Whether the package can be null, for example to disable installing a package altogether (defaults to false) # Whether the package can be null, for example to disable installing a package altogether (defaults to false)
nullable ? false, nullable ? false,
# The attribute path where the default package is located (may be omitted, in which case it is copied from `name`) # The attribute path where the default package is located (may be omitted, in which case it is copied from `name`)
default ? name, default ? name,
# A string or an attribute path to use as an example (may be omitted) # A string or an attribute path to use as an example (may be omitted)
example ? null, example ? null,
# Additional text to include in the option description (may be omitted) # Additional text to include in the option description (may be omitted)
extraDescription ? "", extraDescription ? "",
# Representation of the package set passed as pkgs (defaults to `"pkgs"`) # Representation of the package set passed as pkgs (defaults to `"pkgs"`)
pkgsText ? "pkgs" pkgsText ? "pkgs",
}: }:
let let
name' = if isList name then last name else name; name' = if isList name then last name else name;
default' = if isList default then default else [ default ]; default' = if isList default then default else [ default ];
defaultText = concatStringsSep "." default'; defaultText = concatStringsSep "." default';
defaultValue = attrByPath default' defaultValue = attrByPath default' (throw "${defaultText} cannot be found in ${pkgsText}") pkgs;
(throw "${defaultText} cannot be found in ${pkgsText}") pkgs; defaults =
defaults = if default != null then { if default != null then
default = defaultValue; {
defaultText = literalExpression ("${pkgsText}." + defaultText); default = defaultValue;
} else optionalAttrs nullable { defaultText = literalExpression ("${pkgsText}." + defaultText);
default = null; }
}; else
in mkOption (defaults // { optionalAttrs nullable {
description = "The ${name'} package to use." default = null;
+ (if extraDescription == "" then "" else " ") + extraDescription; };
in
mkOption (
defaults
// {
description =
"The ${name'} package to use." + (if extraDescription == "" then "" else " ") + extraDescription;
type = with lib.types; (if nullable then nullOr else lib.id) package; type = with lib.types; (if nullable then nullOr else lib.id) package;
} // optionalAttrs (example != null) { }
example = literalExpression // optionalAttrs (example != null) {
(if isList example then "${pkgsText}." + concatStringsSep "." example else example); example = literalExpression (
}); if isList example then "${pkgsText}." + concatStringsSep "." example else example
);
}
);
/* Deprecated alias of mkPackageOption, to be removed in 25.05. /*
Previously used to create options with markdown documentation, which is no longer required. Deprecated alias of mkPackageOption, to be removed in 25.05.
Previously used to create options with markdown documentation, which is no longer required.
*/ */
mkPackageOptionMD = lib.warn "mkPackageOptionMD is deprecated and will be removed in 25.05; please use mkPackageOption." mkPackageOption; mkPackageOptionMD = lib.warn "mkPackageOptionMD is deprecated and will be removed in 25.05; please use mkPackageOption." mkPackageOption;
/* This option accepts anything, but it does not produce any result. /*
This option accepts anything, but it does not produce any result.
This is useful for sharing a module across different module sets This is useful for sharing a module across different module sets
without having to implement similar features as long as the without having to implement similar features as long as the
values of the options are not accessed. */ values of the options are not accessed.
mkSinkUndeclaredOptions = attrs: mkOption ({ */
internal = true; mkSinkUndeclaredOptions =
visible = false; attrs:
default = false; mkOption (
description = "Sink for option definitions."; {
type = mkOptionType { internal = true;
name = "sink"; visible = false;
check = x: true; default = false;
merge = loc: defs: false; description = "Sink for option definitions.";
}; type = mkOptionType {
apply = x: throw "Option value is not readable because the option is not declared."; name = "sink";
} // attrs); check = x: true;
merge = loc: defs: false;
};
apply = x: throw "Option value is not readable because the option is not declared.";
}
// attrs
);
mergeDefaultOption = loc: defs: mergeDefaultOption =
let list = getValues defs; in loc: defs:
if length list == 1 then head list let
else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) list = getValues defs;
else if all isList list then concatLists list in
else if all isAttrs list then foldl' lib.mergeAttrs {} list if length list == 1 then
else if all isBool list then foldl' lib.or false list head list
else if all isString list then lib.concatStrings list else if all isFunction list then
else if all isInt list && all (x: x == head list) list then head list x: mergeDefaultOption loc (map (f: f x) list)
else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; else if all isList list then
concatLists list
else if all isAttrs list then
foldl' lib.mergeAttrs { } list
else if all isBool list then
foldl' lib.or false list
else if all isString list then
lib.concatStrings list
else if all isInt list && all (x: x == head list) list then
head list
else
throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
/* /*
Require a single definition. Require a single definition.
WARNING: Does not perform nested checks, as this does not run the merge function! WARNING: Does not perform nested checks, as this does not run the merge function!
*/ */
mergeOneOption = mergeUniqueOption { message = ""; }; mergeOneOption = mergeUniqueOption { message = ""; };
/* /*
Require a single definition. Require a single definition.
NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc). NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
*/ */
mergeUniqueOption = args@{ mergeUniqueOption =
args@{
message, message,
# WARNING: the default merge function assumes that the definition is a valid (option) value. You MUST pass a merge function if the return value needs to be # WARNING: the default merge function assumes that the definition is a valid (option) value. You MUST pass a merge function if the return value needs to be
# - type checked beyond what .check does (which should be very litte; only on the value head; not attribute values, etc) # - type checked beyond what .check does (which should be very litte; only on the value head; not attribute values, etc)
# - if you want attribute values to be checked, or list items # - if you want attribute values to be checked, or list items
# - if you want coercedTo-like behavior to work # - if you want coercedTo-like behavior to work
merge ? loc: defs: (head defs).value }: merge ? loc: defs: (head defs).value,
}:
loc: defs: loc: defs:
if length defs == 1 if length defs == 1 then
then merge loc defs merge loc defs
else else
assert length defs > 1; assert length defs > 1;
throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
/* "Merge" option definitions by checking that they all have the same value. */ # "Merge" option definitions by checking that they all have the same value.
mergeEqualOption = loc: defs: mergeEqualOption =
if defs == [] then abort "This case should never happen." loc: defs:
if defs == [ ] then
abort "This case should never happen."
# Return early if we only have one element # Return early if we only have one element
# This also makes it work for functions, because the foldl' below would try # This also makes it work for functions, because the foldl' below would try
# to compare the first element with itself, which is false for functions # to compare the first element with itself, which is false for functions
else if length defs == 1 then (head defs).value else if length defs == 1 then
else (foldl' (first: def: (head defs).value
if def.value != first.value then else
throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}\n${prioritySuggestion}" (foldl' (
else first: def:
first) (head defs) (tail defs)).value; if def.value != first.value then
throw "The option `${showOption loc}' has conflicting definition values:${
showDefs [
first
def
]
}\n${prioritySuggestion}"
else
first
) (head defs) (tail defs)).value;
/* Extracts values of all "value" keys of the given list. /*
Extracts values of all "value" keys of the given list.
Type: getValues :: [ { value :: a; } ] -> [a] Type: getValues :: [ { value :: a; } ] -> [a]
Example: Example:
getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
getValues [ ] // => [ ] getValues [ ] // => [ ]
*/ */
getValues = map (x: x.value); getValues = map (x: x.value);
/* Extracts values of all "file" keys of the given list /*
Extracts values of all "file" keys of the given list
Type: getFiles :: [ { file :: a; } ] -> [a] Type: getFiles :: [ { file :: a; } ] -> [a]
Example: Example:
getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
getFiles [ ] // => [ ] getFiles [ ] // => [ ]
*/ */
getFiles = map (x: x.file); getFiles = map (x: x.file);
# Generate documentation template from the list of option declaration like # Generate documentation template from the list of option declaration like
# the set generated with filterOptionSets. # the set generated with filterOptionSets.
optionAttrSetToDocList = optionAttrSetToDocList' []; optionAttrSetToDocList = optionAttrSetToDocList' [ ];
optionAttrSetToDocList' = _: options: optionAttrSetToDocList' =
concatMap (opt: _: options:
concatMap (
opt:
let let
name = showOption opt.loc; name = showOption opt.loc;
docOption = { docOption =
loc = opt.loc; {
inherit name; loc = opt.loc;
description = opt.description or null; inherit name;
declarations = filter (x: x != unknownModule) opt.declarations; description = opt.description or null;
internal = opt.internal or false; declarations = filter (x: x != unknownModule) opt.declarations;
visible = internal = opt.internal or false;
if (opt?visible && opt.visible == "shallow") visible = if (opt ? visible && opt.visible == "shallow") then true else opt.visible or true;
then true readOnly = opt.readOnly or false;
else opt.visible or true; type = opt.type.description or "unspecified";
readOnly = opt.readOnly or false; }
type = opt.type.description or "unspecified"; // optionalAttrs (opt ? example) {
} example = builtins.addErrorContext "while evaluating the example of option `${name}`" (
// optionalAttrs (opt ? example) {
example =
builtins.addErrorContext "while evaluating the example of option `${name}`" (
renderOptionValue opt.example renderOptionValue opt.example
); );
} }
// optionalAttrs (opt ? defaultText || opt ? default) { // optionalAttrs (opt ? defaultText || opt ? default) {
default = default = builtins.addErrorContext "while evaluating the ${
builtins.addErrorContext "while evaluating the ${if opt?defaultText then "defaultText" else "default value"} of option `${name}`" ( if opt ? defaultText then "defaultText" else "default value"
renderOptionValue (opt.defaultText or opt.default) } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default));
); }
} // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) {
// optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; inherit (opt) relatedPackages;
};
subOptions = subOptions =
let ss = opt.type.getSubOptions opt.loc; let
in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; ss = opt.type.getSubOptions opt.loc;
in
if ss != { } then optionAttrSetToDocList' opt.loc ss else [ ];
subOptionsVisible = docOption.visible && opt.visible or null != "shallow"; subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
in in
# To find infinite recursion in NixOS option docs: # To find infinite recursion in NixOS option docs:
# builtins.trace opt.loc # builtins.trace opt.loc
[ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options); [ docOption ] ++ optionals subOptionsVisible subOptions
) (collect isOption options);
/*
This function recursively removes all derivation attributes from
`x` except for the `name` attribute.
/* This function recursively removes all derivation attributes from This is to make the generation of `options.xml` much more
`x` except for the `name` attribute. efficient: the XML representation of derivations is very large
(on the order of megabytes) and is not actually used by the
manual generator.
This is to make the generation of `options.xml` much more This function was made obsolete by renderOptionValue and is kept for
efficient: the XML representation of derivations is very large compatibility with out-of-tree code.
(on the order of megabytes) and is not actually used by the
manual generator.
This function was made obsolete by renderOptionValue and is kept for
compatibility with out-of-tree code.
*/ */
scrubOptionValue = x: scrubOptionValue =
x:
if isDerivation x then if isDerivation x then
{ type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } {
else if isList x then map scrubOptionValue x type = "derivation";
else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) drvPath = x.name;
else x; outPath = x.name;
name = x.name;
}
else if isList x then
map scrubOptionValue x
else if isAttrs x then
mapAttrs (n: v: scrubOptionValue v) (removeAttrs x [ "_args" ])
else
x;
/*
/* Ensures that the given option value (default or example) is a `_type`d string Ensures that the given option value (default or example) is a `_type`d string
by rendering Nix values to `literalExpression`s. by rendering Nix values to `literalExpression`s.
*/ */
renderOptionValue = v: renderOptionValue =
if v ? _type && v ? text then v v:
else literalExpression (lib.generators.toPretty { if v ? _type && v ? text then
multiline = true; v
allowPrettyValues = true; else
} v); literalExpression (
lib.generators.toPretty {
multiline = true;
allowPrettyValues = true;
} v
);
/*
/* For use in the `defaultText` and `example` option attributes. Causes the For use in the `defaultText` and `example` option attributes. Causes the
given string to be rendered verbatim in the documentation as Nix code. This given string to be rendered verbatim in the documentation as Nix code. This
is necessary for complex values, e.g. functions, or values that depend on is necessary for complex values, e.g. functions, or values that depend on
other values or packages. other values or packages.
*/ */
literalExpression = text: literalExpression =
if ! isString text then throw "literalExpression expects a string." text:
else { _type = "literalExpression"; inherit text; }; if !isString text then
throw "literalExpression expects a string."
else
{
_type = "literalExpression";
inherit text;
};
literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression; literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression;
/* For use in the `defaultText` and `example` option attributes. Causes the /*
given MD text to be inserted verbatim in the documentation, for when For use in the `defaultText` and `example` option attributes. Causes the
a `literalExpression` would be too hard to read. given MD text to be inserted verbatim in the documentation, for when
a `literalExpression` would be too hard to read.
*/ */
literalMD = text: literalMD =
if ! isString text then throw "literalMD expects a string." text:
else { _type = "literalMD"; inherit text; }; if !isString text then
throw "literalMD expects a string."
else
{
_type = "literalMD";
inherit text;
};
# Helper functions. # Helper functions.
/* Convert an option, described as a list of the option parts to a /*
human-readable version. Convert an option, described as a list of the option parts to a
human-readable version.
Example: Example:
(showOption ["foo" "bar" "baz"]) == "foo.bar.baz" (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
(showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux" (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
(showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable" (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
Placeholders will not be quoted as they are not actual values: Placeholders will not be quoted as they are not actual values:
(showOption ["foo" "*" "bar"]) == "foo.*.bar" (showOption ["foo" "*" "bar"]) == "foo.*.bar"
(showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar" (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
*/ */
showOption = parts: let showOption =
escapeOptionPart = part: parts:
let let
# We assume that these are "special values" and not real configuration data. escapeOptionPart =
# If it is real configuration data, it is rendered incorrectly. part:
specialIdentifiers = [ let
"<name>" # attrsOf (submodule {}) # We assume that these are "special values" and not real configuration data.
"*" # listOf (submodule {}) # If it is real configuration data, it is rendered incorrectly.
"<function body>" # functionTo specialIdentifiers = [
]; "<name>" # attrsOf (submodule {})
in if builtins.elem part specialIdentifiers "*" # listOf (submodule {})
then part "<function body>" # functionTo
else lib.strings.escapeNixIdentifier part; ];
in (concatStringsSep ".") (map escapeOptionPart parts); in
if builtins.elem part specialIdentifiers then part else lib.strings.escapeNixIdentifier part;
in
(concatStringsSep ".") (map escapeOptionPart parts);
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
showDefs = defs: concatMapStrings (def: showDefs =
let defs:
# Pretty print the value for display, if successful concatMapStrings (
prettyEval = builtins.tryEval def:
(lib.generators.toPretty { } let
(lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value)); # Pretty print the value for display, if successful
# Split it into its lines prettyEval = builtins.tryEval (
lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value); lib.generators.toPretty { } (
# Only display the first 5 lines, and indent them for better visibility lib.generators.withRecursion {
value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); depthLimit = 10;
result = throwOnDepthLimit = false;
# Don't print any value if evaluating the value strictly fails } def.value
if ! prettyEval.success then "" )
# Put it on a new line if it consists of multiple );
else if length lines > 1 then ":\n " + value # Split it into its lines
else ": " + value; lines = filter (v: !isList v) (builtins.split "\n" prettyEval.value);
in "\n- In `${def.file}'${result}" # Only display the first 5 lines, and indent them for better visibility
) defs; value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
result =
# Don't print any value if evaluating the value strictly fails
if !prettyEval.success then
""
# Put it on a new line if it consists of multiple
else if length lines > 1 then
":\n " + value
else
": " + value;
in
"\n- In `${def.file}'${result}"
) defs;
showOptionWithDefLocs = opt: '' showOptionWithDefLocs = opt: ''
${showOption opt.loc}, with values defined in: ${showOption opt.loc}, with values defined in:
${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
''; '';
unknownModule = "<unknown-file>"; unknownModule = "<unknown-file>";

View file

@ -1,44 +1,44 @@
{ lib }: { lib }:
/* /*
Usage: Usage:
You define you custom builder script by adding all build steps to a list. You define you custom builder script by adding all build steps to a list.
for example: for example:
builder = writeScript "fsg-4.4-builder" builder = writeScript "fsg-4.4-builder"
(textClosure [doUnpack addInputs preBuild doMake installPhase doForceShare]); (textClosure [doUnpack addInputs preBuild doMake installPhase doForceShare]);
a step is defined by noDepEntry, fullDepEntry or packEntry. a step is defined by noDepEntry, fullDepEntry or packEntry.
To ensure that prerequisite are met those are added before the task itself by To ensure that prerequisite are met those are added before the task itself by
textClosureDupList. Duplicated items are removed again. textClosureDupList. Duplicated items are removed again.
See trace/nixpkgs/trunk/pkgs/top-level/builder-defs.nix for some predefined build steps See trace/nixpkgs/trunk/pkgs/top-level/builder-defs.nix for some predefined build steps
Attention: Attention:
let let
pkgs = (import <nixpkgs>) {}; pkgs = (import <nixpkgs>) {};
in let in let
inherit (pkgs.stringsWithDeps) fullDepEntry packEntry noDepEntry textClosureMap; inherit (pkgs.stringsWithDeps) fullDepEntry packEntry noDepEntry textClosureMap;
inherit (pkgs.lib) id; inherit (pkgs.lib) id;
nameA = noDepEntry "Text a"; nameA = noDepEntry "Text a";
nameB = fullDepEntry "Text b" ["nameA"]; nameB = fullDepEntry "Text b" ["nameA"];
nameC = fullDepEntry "Text c" ["nameA"]; nameC = fullDepEntry "Text c" ["nameA"];
stages = { stages = {
nameHeader = noDepEntry "#! /bin/sh \n"; nameHeader = noDepEntry "#! /bin/sh \n";
inherit nameA nameB nameC; inherit nameA nameB nameC;
}; };
in in
textClosureMap id stages textClosureMap id stages
[ "nameHeader" "nameA" "nameB" "nameC" [ "nameHeader" "nameA" "nameB" "nameC"
nameC # <- added twice. add a dep entry if you know that it will be added once only [1] nameC # <- added twice. add a dep entry if you know that it will be added once only [1]
"nameB" # <- this will not be added again because the attr name (reference) is used "nameB" # <- this will not be added again because the attr name (reference) is used
] ]
# result: Str("#! /bin/sh \n\nText a\nText b\nText c\nText c",[]) # result: Str("#! /bin/sh \n\nText a\nText b\nText c\nText c",[])
[1] maybe this behaviour should be removed to keep things simple (?) [1] maybe this behaviour should be removed to keep things simple (?)
*/ */
let let
@ -52,32 +52,63 @@ let
in in
rec { rec {
/* !!! The interface of this function is kind of messed up, since /*
it's way too overloaded and almost but not quite computes a !!! The interface of this function is kind of messed up, since
topological sort of the depstrings. */ it's way too overloaded and almost but not quite computes a
topological sort of the depstrings.
*/
textClosureList = predefined: arg: textClosureList =
predefined: arg:
let let
f = done: todo: f =
if todo == [] then {result = []; inherit done;} done: todo:
if todo == [ ] then
{
result = [ ];
inherit done;
}
else else
let entry = head todo; in let
entry = head todo;
in
if isAttrs entry then if isAttrs entry then
let x = f done entry.deps; let
y = f x.done (tail todo); x = f done entry.deps;
in { result = x.result ++ [entry.text] ++ y.result; y = f x.done (tail todo);
done = y.done; in
} {
else if done ? ${entry} then f done (tail todo) result = x.result ++ [ entry.text ] ++ y.result;
else f (done // listToAttrs [{name = entry; value = 1;}]) ([predefined.${entry}] ++ tail todo); done = y.done;
in (f {} arg).result; }
else if done ? ${entry} then
f done (tail todo)
else
f (
done
// listToAttrs [
{
name = entry;
value = 1;
}
]
) ([ predefined.${entry} ] ++ tail todo);
in
(f { } arg).result;
textClosureMap = f: predefined: names: textClosureMap =
f: predefined: names:
concatStringsSep "\n" (map f (textClosureList predefined names)); concatStringsSep "\n" (map f (textClosureList predefined names));
noDepEntry = text: {inherit text; deps = [];}; noDepEntry = text: {
fullDepEntry = text: deps: {inherit text deps;}; inherit text;
packEntry = deps: {inherit deps; text="";}; deps = [ ];
};
fullDepEntry = text: deps: { inherit text deps; };
packEntry = deps: {
inherit deps;
text = "";
};
stringAfter = deps: text: { inherit text deps; }; stringAfter = deps: text: { inherit text deps; };

File diff suppressed because it is too large Load diff

View file

@ -5,90 +5,372 @@ rec {
features = { features = {
# x86_64 Generic # x86_64 Generic
# Spec: https://gitlab.com/x86-psABIs/x86-64-ABI/ # Spec: https://gitlab.com/x86-psABIs/x86-64-ABI/
default = [ ]; default = [ ];
x86-64 = [ ]; x86-64 = [ ];
x86-64-v2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" ]; x86-64-v2 = [
x86-64-v3 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" "avx2" "fma" ]; "sse3"
x86-64-v4 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" "avx2" "avx512" "fma" ]; "ssse3"
"sse4_1"
"sse4_2"
];
x86-64-v3 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"avx"
"avx2"
"fma"
];
x86-64-v4 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"avx"
"avx2"
"avx512"
"fma"
];
# x86_64 Intel # x86_64 Intel
nehalem = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" ]; nehalem = [
westmere = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" ]; "sse3"
sandybridge = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" ]; "ssse3"
ivybridge = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" ]; "sse4_1"
haswell = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; "sse4_2"
broadwell = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; "aes"
skylake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; ];
skylake-avx512 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; westmere = [
cannonlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "sse3"
icelake-client = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "ssse3"
icelake-server = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "sse4_1"
cascadelake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "sse4_2"
cooperlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "aes"
tigerlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; ];
alderlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; sandybridge = [
sapphirerapids = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "sse3"
emeraldrapids = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; "ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
];
ivybridge = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
];
haswell = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"fma"
];
broadwell = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"fma"
];
skylake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"fma"
];
skylake-avx512 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
cannonlake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
icelake-client = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
icelake-server = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
cascadelake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
cooperlake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
tigerlake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
alderlake = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"fma"
];
sapphirerapids = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
emeraldrapids = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
# x86_64 AMD # x86_64 AMD
btver1 = [ "sse3" "ssse3" "sse4_1" "sse4_2" ]; btver1 = [
btver2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" ]; "sse3"
bdver1 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; "ssse3"
bdver2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; "sse4_1"
bdver3 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; "sse4_2"
bdver4 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" "fma4" ]; ];
znver1 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; btver2 = [
znver2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; "sse3"
znver3 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; "ssse3"
znver4 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "avx512" "fma" ]; "sse4_1"
"sse4_2"
"aes"
"avx"
];
bdver1 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"fma"
"fma4"
];
bdver2 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"fma"
"fma4"
];
bdver3 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"fma"
"fma4"
];
bdver4 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"avx2"
"fma"
"fma4"
];
znver1 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"avx2"
"fma"
];
znver2 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"avx2"
"fma"
];
znver3 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"avx2"
"fma"
];
znver4 = [
"sse3"
"ssse3"
"sse4_1"
"sse4_2"
"sse4a"
"aes"
"avx"
"avx2"
"avx512"
"fma"
];
# other # other
armv5te = [ ]; armv5te = [ ];
armv6 = [ ]; armv6 = [ ];
armv7-a = [ ]; armv7-a = [ ];
armv8-a = [ ]; armv8-a = [ ];
mips32 = [ ]; mips32 = [ ];
loongson2f = [ ]; loongson2f = [ ];
}; };
# a superior CPU has all the features of an inferior and is able to build and test code for it # a superior CPU has all the features of an inferior and is able to build and test code for it
inferiors = { inferiors = {
# x86_64 Generic # x86_64 Generic
default = [ ]; default = [ ];
x86-64 = [ ]; x86-64 = [ ];
x86-64-v2 = [ "x86-64" ]; x86-64-v2 = [ "x86-64" ];
x86-64-v3 = [ "x86-64-v2" ] ++ inferiors.x86-64-v2; x86-64-v3 = [ "x86-64-v2" ] ++ inferiors.x86-64-v2;
x86-64-v4 = [ "x86-64-v3" ] ++ inferiors.x86-64-v3; x86-64-v4 = [ "x86-64-v3" ] ++ inferiors.x86-64-v3;
# x86_64 Intel # x86_64 Intel
# https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
nehalem = [ "x86-64-v2" ] ++ inferiors.x86-64-v2; nehalem = [ "x86-64-v2" ] ++ inferiors.x86-64-v2;
westmere = [ "nehalem" ] ++ inferiors.nehalem; westmere = [ "nehalem" ] ++ inferiors.nehalem;
sandybridge = [ "westmere" ] ++ inferiors.westmere; sandybridge = [ "westmere" ] ++ inferiors.westmere;
ivybridge = [ "sandybridge" ] ++ inferiors.sandybridge; ivybridge = [ "sandybridge" ] ++ inferiors.sandybridge;
haswell = lib.unique ([ "ivybridge" "x86-64-v3" ] ++ inferiors.ivybridge ++ inferiors.x86-64-v3); haswell = lib.unique (
broadwell = [ "haswell" ] ++ inferiors.haswell; [
skylake = [ "broadwell" ] ++ inferiors.broadwell; "ivybridge"
"x86-64-v3"
]
++ inferiors.ivybridge
++ inferiors.x86-64-v3
);
broadwell = [ "haswell" ] ++ inferiors.haswell;
skylake = [ "broadwell" ] ++ inferiors.broadwell;
skylake-avx512 = lib.unique ([ "skylake" "x86-64-v4" ] ++ inferiors.skylake ++ inferiors.x86-64-v4); skylake-avx512 = lib.unique (
cannonlake = [ "skylake-avx512" ] ++ inferiors.skylake-avx512; [
icelake-client = [ "cannonlake" ] ++ inferiors.cannonlake; "skylake"
"x86-64-v4"
]
++ inferiors.skylake
++ inferiors.x86-64-v4
);
cannonlake = [ "skylake-avx512" ] ++ inferiors.skylake-avx512;
icelake-client = [ "cannonlake" ] ++ inferiors.cannonlake;
icelake-server = [ "icelake-client" ] ++ inferiors.icelake-client; icelake-server = [ "icelake-client" ] ++ inferiors.icelake-client;
cascadelake = [ "cannonlake" ] ++ inferiors.cannonlake; cascadelake = [ "cannonlake" ] ++ inferiors.cannonlake;
cooperlake = [ "cascadelake" ] ++ inferiors.cascadelake; cooperlake = [ "cascadelake" ] ++ inferiors.cascadelake;
tigerlake = [ "icelake-server" ] ++ inferiors.icelake-server; tigerlake = [ "icelake-server" ] ++ inferiors.icelake-server;
sapphirerapids = [ "tigerlake" ] ++ inferiors.tigerlake; sapphirerapids = [ "tigerlake" ] ++ inferiors.tigerlake;
emeraldrapids = [ "sapphirerapids" ] ++ inferiors.sapphirerapids; emeraldrapids = [ "sapphirerapids" ] ++ inferiors.sapphirerapids;
# CX16 does not exist on alderlake, while it does on nearly all other intel CPUs # CX16 does not exist on alderlake, while it does on nearly all other intel CPUs
alderlake = [ ]; alderlake = [ ];
# x86_64 AMD # x86_64 AMD
# TODO: fill this (need testing) # TODO: fill this (need testing)
btver1 = [ ]; btver1 = [ ];
btver2 = [ ]; btver2 = [ ];
bdver1 = [ ]; bdver1 = [ ];
bdver2 = [ ]; bdver2 = [ ];
bdver3 = [ ]; bdver3 = [ ];
bdver4 = [ ]; bdver4 = [ ];
# Regarding `skylake` as inferior of `znver1`, there are reports of # Regarding `skylake` as inferior of `znver1`, there are reports of
# successful usage by Gentoo users and Phoronix benchmarking of different # successful usage by Gentoo users and Phoronix benchmarking of different
# `-march` targets. # `-march` targets.
@ -108,33 +390,42 @@ rec {
# https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
# https://en.wikichip.org/wiki/amd/microarchitectures/zen # https://en.wikichip.org/wiki/amd/microarchitectures/zen
# https://en.wikichip.org/wiki/intel/microarchitectures/skylake # https://en.wikichip.org/wiki/intel/microarchitectures/skylake
znver1 = [ "skylake" ] ++ inferiors.skylake; # Includes haswell and x86-64-v3 znver1 = [ "skylake" ] ++ inferiors.skylake; # Includes haswell and x86-64-v3
znver2 = [ "znver1" ] ++ inferiors.znver1; znver2 = [ "znver1" ] ++ inferiors.znver1;
znver3 = [ "znver2" ] ++ inferiors.znver2; znver3 = [ "znver2" ] ++ inferiors.znver2;
znver4 = lib.unique ([ "znver3" "x86-64-v4" ] ++ inferiors.znver3 ++ inferiors.x86-64-v4); znver4 = lib.unique (
[
"znver3"
"x86-64-v4"
]
++ inferiors.znver3
++ inferiors.x86-64-v4
);
# other # other
armv5te = [ ]; armv5te = [ ];
armv6 = [ ]; armv6 = [ ];
armv7-a = [ ]; armv7-a = [ ];
armv8-a = [ ]; armv8-a = [ ];
mips32 = [ ]; mips32 = [ ];
loongson2f = [ ]; loongson2f = [ ];
}; };
predicates = let predicates =
featureSupport = feature: x: builtins.elem feature features.${x} or []; let
in { featureSupport = feature: x: builtins.elem feature features.${x} or [ ];
sse3Support = featureSupport "sse3"; in
ssse3Support = featureSupport "ssse3"; {
sse4_1Support = featureSupport "sse4_1"; sse3Support = featureSupport "sse3";
sse4_2Support = featureSupport "sse4_2"; ssse3Support = featureSupport "ssse3";
sse4_aSupport = featureSupport "sse4a"; sse4_1Support = featureSupport "sse4_1";
avxSupport = featureSupport "avx"; sse4_2Support = featureSupport "sse4_2";
avx2Support = featureSupport "avx2"; sse4_aSupport = featureSupport "sse4a";
avx512Support = featureSupport "avx512"; avxSupport = featureSupport "avx";
aesSupport = featureSupport "aes"; avx2Support = featureSupport "avx2";
fmaSupport = featureSupport "fma"; avx512Support = featureSupport "avx512";
fma4Support = featureSupport "fma4"; aesSupport = featureSupport "aes";
}; fmaSupport = featureSupport "fma";
fma4Support = featureSupport "fma4";
};
} }

View file

@ -42,8 +42,10 @@ let
both arguments have been `elaborate`-d. both arguments have been `elaborate`-d.
*/ */
equals = equals =
let removeFunctions = a: filterAttrs (_: v: !isFunction v) a; let
in a: b: removeFunctions a == removeFunctions b; removeFunctions = a: filterAttrs (_: v: !isFunction v) a;
in
a: b: removeFunctions a == removeFunctions b;
/** /**
List of all Nix system doubles the nixpkgs flake will expose the package set List of all Nix system doubles the nixpkgs flake will expose the package set
@ -61,364 +63,467 @@ let
# `parsed` is inferred from args, both because there are two options with one # `parsed` is inferred from args, both because there are two options with one
# clearly preferred, and to prevent cycles. A simpler fixed point where the RHS # clearly preferred, and to prevent cycles. A simpler fixed point where the RHS
# always just used `final.*` would fail on both counts. # always just used `final.*` would fail on both counts.
elaborate = args': let elaborate =
args = if isString args' then { system = args'; } args':
else args'; let
args = if isString args' then { system = args'; } else args';
# TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
rust = args.rust or args.rustc or {}; rust = args.rust or args.rustc or { };
final = { final =
# Prefer to parse `config` as it is strictly more informative. {
parsed = parse.mkSystemFromString (if args ? config then args.config else args.system); # Prefer to parse `config` as it is strictly more informative.
# Either of these can be losslessly-extracted from `parsed` iff parsing succeeds. parsed = parse.mkSystemFromString (if args ? config then args.config else args.system);
system = parse.doubleFromSystem final.parsed; # Either of these can be losslessly-extracted from `parsed` iff parsing succeeds.
config = parse.tripleFromSystem final.parsed; system = parse.doubleFromSystem final.parsed;
# Determine whether we can execute binaries built for the provided platform. config = parse.tripleFromSystem final.parsed;
canExecute = platform: # Determine whether we can execute binaries built for the provided platform.
final.isAndroid == platform.isAndroid && canExecute =
parse.isCompatible final.parsed.cpu platform.parsed.cpu platform:
&& final.parsed.kernel == platform.parsed.kernel; final.isAndroid == platform.isAndroid
isCompatible = _: throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details"; && parse.isCompatible final.parsed.cpu platform.parsed.cpu
# Derived meta-data && final.parsed.kernel == platform.parsed.kernel;
useLLVM = final.isFreeBSD || final.isOpenBSD; isCompatible =
_:
throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details";
# Derived meta-data
useLLVM = final.isFreeBSD || final.isOpenBSD;
libc = libc =
/**/ if final.isDarwin then "libSystem" if final.isDarwin then
else if final.isMinGW then "msvcrt" "libSystem"
else if final.isWasi then "wasilibc" else if final.isMinGW then
else if final.isWasm && !final.isWasi then null "msvcrt"
else if final.isRedox then "relibc" else if final.isWasi then
else if final.isMusl then "musl" "wasilibc"
else if final.isUClibc then "uclibc" else if final.isWasm && !final.isWasi then
else if final.isAndroid then "bionic" null
else if final.isLinux /* default */ then "glibc" else if final.isRedox then
else if final.isFreeBSD then "fblibc" "relibc"
else if final.isOpenBSD then "oblibc" else if final.isMusl then
else if final.isNetBSD then "nblibc" "musl"
else if final.isAvr then "avrlibc" else if final.isUClibc then
else if final.isGhcjs then null "uclibc"
else if final.isNone then "newlib" else if final.isAndroid then
# TODO(@Ericson2314) think more about other operating systems "bionic"
else "native/impure"; else if
# Choose what linker we wish to use by default. Someday we might also final.isLinux # default
# choose the C compiler, runtime library, C++ standard library, etc. in then
# this way, nice and orthogonally, and deprecate `useLLVM`. But due to "glibc"
# the monolithic GCC build we cannot actually make those choices else if final.isFreeBSD then
# independently, so we are just doing `linker` and keeping `useLLVM` for "fblibc"
# now. else if final.isOpenBSD then
linker = "oblibc"
/**/ if final.useLLVM or false then "lld" else if final.isNetBSD then
else if final.isDarwin then "cctools" "nblibc"
# "bfd" and "gold" both come from GNU binutils. The existence of Gold else if final.isAvr then
# is why we use the more obscure "bfd" and not "binutils" for this "avrlibc"
# choice. else if final.isGhcjs then
else "bfd"; null
# The standard lib directory name that non-nixpkgs binaries distributed else if final.isNone then
# for this platform normally assume. "newlib"
libDir = if final.isLinux then # TODO(@Ericson2314) think more about other operating systems
if final.isx86_64 || final.isMips64 || final.isPower64 else
then "lib64" "native/impure";
else "lib" # Choose what linker we wish to use by default. Someday we might also
else null; # choose the C compiler, runtime library, C++ standard library, etc. in
extensions = optionalAttrs final.hasSharedLibraries { # this way, nice and orthogonally, and deprecate `useLLVM`. But due to
sharedLibrary = # the monolithic GCC build we cannot actually make those choices
if final.isDarwin then ".dylib" # independently, so we are just doing `linker` and keeping `useLLVM` for
else if final.isWindows then ".dll" # now.
else ".so"; linker =
} // { if final.useLLVM or false then
staticLibrary = "lld"
/**/ if final.isWindows then ".lib" else if final.isDarwin then
else ".a"; "cctools"
library = # "bfd" and "gold" both come from GNU binutils. The existence of Gold
/**/ if final.isStatic then final.extensions.staticLibrary # is why we use the more obscure "bfd" and not "binutils" for this
else final.extensions.sharedLibrary; # choice.
executable = else
/**/ if final.isWindows then ".exe" "bfd";
else ""; # The standard lib directory name that non-nixpkgs binaries distributed
}; # for this platform normally assume.
# Misc boolean options libDir =
useAndroidPrebuilt = false; if final.isLinux then
useiOSPrebuilt = false; if final.isx86_64 || final.isMips64 || final.isPower64 then "lib64" else "lib"
else
null;
extensions =
optionalAttrs final.hasSharedLibraries {
sharedLibrary =
if final.isDarwin then
".dylib"
else if final.isWindows then
".dll"
else
".so";
}
// {
staticLibrary = if final.isWindows then ".lib" else ".a";
library = if final.isStatic then final.extensions.staticLibrary else final.extensions.sharedLibrary;
executable = if final.isWindows then ".exe" else "";
};
# Misc boolean options
useAndroidPrebuilt = false;
useiOSPrebuilt = false;
# Output from uname # Output from uname
uname = { uname = {
# uname -s # uname -s
system = { system =
linux = "Linux"; {
windows = "Windows"; linux = "Linux";
darwin = "Darwin"; windows = "Windows";
netbsd = "NetBSD"; darwin = "Darwin";
freebsd = "FreeBSD"; netbsd = "NetBSD";
openbsd = "OpenBSD"; freebsd = "FreeBSD";
wasi = "Wasi"; openbsd = "OpenBSD";
redox = "Redox"; wasi = "Wasi";
genode = "Genode"; redox = "Redox";
}.${final.parsed.kernel.name} or null; genode = "Genode";
}
.${final.parsed.kernel.name} or null;
# uname -m # uname -m
processor = processor =
if final.isPower64 if final.isPower64 then
then "ppc64${optionalString final.isLittleEndian "le"}" "ppc64${optionalString final.isLittleEndian "le"}"
else if final.isPower else if final.isPower then
then "ppc${optionalString final.isLittleEndian "le"}" "ppc${optionalString final.isLittleEndian "le"}"
else if final.isMips64 else if final.isMips64 then
then "mips64" # endianness is *not* included on mips64 "mips64" # endianness is *not* included on mips64
else final.parsed.cpu.name; else
final.parsed.cpu.name;
# uname -r # uname -r
release = null; release = null;
};
# It is important that hasSharedLibraries==false when the platform has no
# dynamic library loader. Various tools (including the gcc build system)
# have knowledge of which platforms are incapable of dynamic linking, and
# will still build on/for those platforms with --enable-shared, but simply
# omit any `.so` build products such as libgcc_s.so. When that happens,
# it causes hard-to-troubleshoot build failures.
hasSharedLibraries = with final;
(isAndroid || isGnu || isMusl # Linux (allows multiple libcs)
|| isDarwin || isSunOS || isOpenBSD || isFreeBSD || isNetBSD # BSDs
|| isCygwin || isMinGW || isWindows # Windows
|| isWasm # WASM
) && !isStatic;
# The difference between `isStatic` and `hasSharedLibraries` is mainly the
# addition of the `staticMarker` (see make-derivation.nix). Some
# platforms, like embedded machines without a libc (e.g. arm-none-eabi)
# don't support dynamic linking, but don't get the `staticMarker`.
# `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
# has the `staticMarker`.
isStatic = final.isWasi || final.isRedox;
# Just a guess, based on `system`
inherit
({
linux-kernel = args.linux-kernel or {};
gcc = args.gcc or {};
} // platforms.select final)
linux-kernel gcc;
# TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
rustc = args.rustc or {};
linuxArch =
if final.isAarch32 then "arm"
else if final.isAarch64 then "arm64"
else if final.isx86_32 then "i386"
else if final.isx86_64 then "x86_64"
# linux kernel does not distinguish microblaze/microblazeel
else if final.isMicroBlaze then "microblaze"
else if final.isMips32 then "mips"
else if final.isMips64 then "mips" # linux kernel does not distinguish mips32/mips64
else if final.isPower then "powerpc"
else if final.isRiscV then "riscv"
else if final.isS390 then "s390"
else if final.isLoongArch64 then "loongarch"
else final.parsed.cpu.name;
# https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106
ubootArch =
if final.isx86_32 then "x86" # not i386
else if final.isMips64 then "mips64" # uboot *does* distinguish between mips32/mips64
else final.linuxArch; # other cases appear to agree with linuxArch
qemuArch =
if final.isAarch32 then "arm"
else if final.isS390 && !final.isS390x then null
else if final.isx86_64 then "x86_64"
else if final.isx86 then "i386"
else if final.isMips64n32 then "mipsn32${optionalString final.isLittleEndian "el"}"
else if final.isMips64 then "mips64${optionalString final.isLittleEndian "el"}"
else final.uname.processor;
# Name used by UEFI for architectures.
efiArch =
if final.isx86_32 then "ia32"
else if final.isx86_64 then "x64"
else if final.isAarch32 then "arm"
else if final.isAarch64 then "aa64"
else final.parsed.cpu.name;
darwinArch = {
armv7a = "armv7";
aarch64 = "arm64";
}.${final.parsed.cpu.name} or final.parsed.cpu.name;
darwinPlatform =
if final.isMacOS then "macos"
else if final.isiOS then "ios"
else null;
# The canonical name for this attribute is darwinSdkVersion, but some
# platforms define the old name "sdkVer".
darwinSdkVersion = final.sdkVer or (if final.isAarch64 then "11.0" else "10.12");
darwinMinVersion = final.darwinSdkVersion;
darwinMinVersionVariable =
if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET"
else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET"
else null;
# Remove before 25.05
androidSdkVersion =
if (args ? sdkVer && !args ? androidSdkVersion) then
throw "For android `sdkVer` has been renamed to `androidSdkVersion`"
else if (args ? androidSdkVersion) then
args.androidSdkVersion
else
null;
androidNdkVersion =
if (args ? ndkVer && !args ? androidNdkVersion) then
throw "For android `ndkVer` has been renamed to `androidNdkVersion`"
else if (args ? androidSdkVersion) then
args.androidNdkVersion
else
null;
} // (
let
selectEmulator = pkgs:
let
wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
in
# Note: we guarantee that the return value is either `null` or a path
# to an emulator program. That is, if an emulator requires additional
# arguments, a wrapper should be used.
if pkgs.stdenv.hostPlatform.canExecute final
then "${pkgs.execline}/bin/exec"
else if final.isWindows
then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null
then "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}"
else if final.isWasi
then "${pkgs.wasmtime}/bin/wasmtime"
else if final.isMmix
then "${pkgs.mmixware}/bin/mmix"
else null;
in {
emulatorAvailable = pkgs: (selectEmulator pkgs) != null;
# whether final.emulator pkgs.pkgsStatic works
staticEmulatorAvailable = pkgs: final.emulatorAvailable pkgs
&& (final.isLinux || final.isWasi || final.isMmix);
emulator = pkgs:
if (final.emulatorAvailable pkgs)
then selectEmulator pkgs
else throw "Don't know how to run ${final.config} executables.";
}) // mapAttrs (n: v: v final.parsed) inspect.predicates
// mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
// args // {
rust = rust // {
# Once args.rustc.platform.target-family is deprecated and
# removed, there will no longer be any need to modify any
# values from args.rust.platform, so we can drop all the
# "args ? rust" etc. checks, and merge args.rust.platform in
# /after/.
platform = rust.platform or {} // {
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
arch =
/**/ if rust ? platform then rust.platform.arch
else if final.isAarch32 then "arm"
else if final.isMips64 then "mips64" # never add "el" suffix
else if final.isPower64 then "powerpc64" # never add "le" suffix
else final.parsed.cpu.name;
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
os =
/**/ if rust ? platform then rust.platform.os or "none"
else if final.isDarwin then "macos"
else if final.isWasm && !final.isWasi then "unknown" # Needed for {wasm32,wasm64}-unknown-unknown.
else final.parsed.kernel.name;
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
target-family =
/**/ if args ? rust.platform.target-family then args.rust.platform.target-family
else if args ? rustc.platform.target-family
then
(
# Since https://github.com/rust-lang/rust/pull/84072
# `target-family` is a list instead of single value.
let
f = args.rustc.platform.target-family;
in
if isList f then f else [ f ]
)
else optional final.isUnix "unix"
++ optional final.isWindows "windows"
++ optional final.isWasm "wasm";
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
vendor = let
inherit (final.parsed) vendor;
in rust.platform.vendor or {
"w64" = "pc";
}.${vendor.name} or vendor.name;
}; };
# The name of the rust target, even if it is custom. Adjustments are # It is important that hasSharedLibraries==false when the platform has no
# because rust has slightly different naming conventions than we do. # dynamic library loader. Various tools (including the gcc build system)
rustcTarget = let # have knowledge of which platforms are incapable of dynamic linking, and
inherit (final.parsed) cpu kernel abi; # will still build on/for those platforms with --enable-shared, but simply
cpu_ = rust.platform.arch or { # omit any `.so` build products such as libgcc_s.so. When that happens,
"armv7a" = "armv7"; # it causes hard-to-troubleshoot build failures.
"armv7l" = "armv7"; hasSharedLibraries =
"armv6l" = "arm"; with final;
"armv5tel" = "armv5te"; (
"riscv32" = "riscv32gc"; isAndroid
"riscv64" = "riscv64gc"; || isGnu
}.${cpu.name} or cpu.name; || isMusl # Linux (allows multiple libcs)
vendor_ = final.rust.platform.vendor; || isDarwin
# TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. || isSunOS
|| isOpenBSD
|| isFreeBSD
|| isNetBSD # BSDs
|| isCygwin
|| isMinGW
|| isWindows # Windows
|| isWasm # WASM
)
&& !isStatic;
# The difference between `isStatic` and `hasSharedLibraries` is mainly the
# addition of the `staticMarker` (see make-derivation.nix). Some
# platforms, like embedded machines without a libc (e.g. arm-none-eabi)
# don't support dynamic linking, but don't get the `staticMarker`.
# `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
# has the `staticMarker`.
isStatic = final.isWasi || final.isRedox;
# Just a guess, based on `system`
inherit
(
{
linux-kernel = args.linux-kernel or { };
gcc = args.gcc or { };
}
// platforms.select final
)
linux-kernel
gcc
;
# TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
rustc = args.rustc or { };
linuxArch =
if final.isAarch32 then
"arm"
else if final.isAarch64 then
"arm64"
else if final.isx86_32 then
"i386"
else if final.isx86_64 then
"x86_64"
# linux kernel does not distinguish microblaze/microblazeel
else if final.isMicroBlaze then
"microblaze"
else if final.isMips32 then
"mips"
else if final.isMips64 then
"mips" # linux kernel does not distinguish mips32/mips64
else if final.isPower then
"powerpc"
else if final.isRiscV then
"riscv"
else if final.isS390 then
"s390"
else if final.isLoongArch64 then
"loongarch"
else
final.parsed.cpu.name;
# https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106
ubootArch =
if final.isx86_32 then
"x86" # not i386
else if final.isMips64 then
"mips64" # uboot *does* distinguish between mips32/mips64
else
final.linuxArch; # other cases appear to agree with linuxArch
qemuArch =
if final.isAarch32 then
"arm"
else if final.isS390 && !final.isS390x then
null
else if final.isx86_64 then
"x86_64"
else if final.isx86 then
"i386"
else if final.isMips64n32 then
"mipsn32${optionalString final.isLittleEndian "el"}"
else if final.isMips64 then
"mips64${optionalString final.isLittleEndian "el"}"
else
final.uname.processor;
# Name used by UEFI for architectures.
efiArch =
if final.isx86_32 then
"ia32"
else if final.isx86_64 then
"x64"
else if final.isAarch32 then
"arm"
else if final.isAarch64 then
"aa64"
else
final.parsed.cpu.name;
darwinArch =
{
armv7a = "armv7";
aarch64 = "arm64";
}
.${final.parsed.cpu.name} or final.parsed.cpu.name;
darwinPlatform =
if final.isMacOS then
"macos"
else if final.isiOS then
"ios"
else
null;
# The canonical name for this attribute is darwinSdkVersion, but some
# platforms define the old name "sdkVer".
darwinSdkVersion = final.sdkVer or (if final.isAarch64 then "11.0" else "10.12");
darwinMinVersion = final.darwinSdkVersion;
darwinMinVersionVariable =
if final.isMacOS then
"MACOSX_DEPLOYMENT_TARGET"
else if final.isiOS then
"IPHONEOS_DEPLOYMENT_TARGET"
else
null;
# Remove before 25.05
androidSdkVersion =
if (args ? sdkVer && !args ? androidSdkVersion) then
throw "For android `sdkVer` has been renamed to `androidSdkVersion`"
else if (args ? androidSdkVersion) then
args.androidSdkVersion
else
null;
androidNdkVersion =
if (args ? ndkVer && !args ? androidNdkVersion) then
throw "For android `ndkVer` has been renamed to `androidNdkVersion`"
else if (args ? androidSdkVersion) then
args.androidNdkVersion
else
null;
}
// (
let
selectEmulator =
pkgs:
let
wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
in
# Note: we guarantee that the return value is either `null` or a path
# to an emulator program. That is, if an emulator requires additional
# arguments, a wrapper should be used.
if pkgs.stdenv.hostPlatform.canExecute final then
"${pkgs.execline}/bin/exec"
else if final.isWindows then
"${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null then
"${pkgs.qemu-user}/bin/qemu-${final.qemuArch}"
else if final.isWasi then
"${pkgs.wasmtime}/bin/wasmtime"
else if final.isMmix then
"${pkgs.mmixware}/bin/mmix"
else
null;
in in
args.rust.rustcTarget or {
args.rustc.config or ( emulatorAvailable = pkgs: (selectEmulator pkgs) != null;
# Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`.
# We cannot know which subversion does the user want, and
# currently use WASI 0.1 as default for compatibility. Custom
# users can set `rust.rustcTarget` to override it.
if final.isWasi
then "${cpu_}-wasip1"
else "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}"
);
# The name of the rust target if it is standard, or the json file # whether final.emulator pkgs.pkgsStatic works
# containing the custom target spec. staticEmulatorAvailable =
rustcTargetSpec = rust.rustcTargetSpec or ( pkgs: final.emulatorAvailable pkgs && (final.isLinux || final.isWasi || final.isMmix);
/**/ if rust ? platform
then builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform)
else final.rust.rustcTarget);
# The name of the rust target if it is standard, or the emulator =
# basename of the file containing the custom target spec, pkgs:
# without the .json extension. if (final.emulatorAvailable pkgs) then
# selectEmulator pkgs
# This is the name used by Cargo for target subdirectories. else
cargoShortTarget = throw "Don't know how to run ${final.config} executables.";
removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
# When used as part of an environment variable name, triples are }
# uppercased and have all hyphens replaced by underscores: )
# // mapAttrs (n: v: v final.parsed) inspect.predicates
# https://github.com/rust-lang/cargo/pull/9169 // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
# https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431 // args
cargoEnvVarTarget = // {
replaceStrings ["-"] ["_"] rust = rust // {
(toUpper final.rust.cargoShortTarget); # Once args.rustc.platform.target-family is deprecated and
# removed, there will no longer be any need to modify any
# values from args.rust.platform, so we can drop all the
# "args ? rust" etc. checks, and merge args.rust.platform in
# /after/.
platform = rust.platform or { } // {
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
arch =
if rust ? platform then
rust.platform.arch
else if final.isAarch32 then
"arm"
else if final.isMips64 then
"mips64" # never add "el" suffix
else if final.isPower64 then
"powerpc64" # never add "le" suffix
else
final.parsed.cpu.name;
# True if the target is no_std # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
# https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421 os =
isNoStdTarget = if rust ? platform then
any (t: hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"]; rust.platform.os or "none"
else if final.isDarwin then
"macos"
else if final.isWasm && !final.isWasi then
"unknown" # Needed for {wasm32,wasm64}-unknown-unknown.
else
final.parsed.kernel.name;
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
target-family =
if args ? rust.platform.target-family then
args.rust.platform.target-family
else if args ? rustc.platform.target-family then
(
# Since https://github.com/rust-lang/rust/pull/84072
# `target-family` is a list instead of single value.
let
f = args.rustc.platform.target-family;
in
if isList f then f else [ f ]
)
else
optional final.isUnix "unix" ++ optional final.isWindows "windows" ++ optional final.isWasm "wasm";
# https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
vendor =
let
inherit (final.parsed) vendor;
in
rust.platform.vendor or {
"w64" = "pc";
}
.${vendor.name} or vendor.name;
};
# The name of the rust target, even if it is custom. Adjustments are
# because rust has slightly different naming conventions than we do.
rustcTarget =
let
inherit (final.parsed) cpu kernel abi;
cpu_ =
rust.platform.arch or {
"armv7a" = "armv7";
"armv7l" = "armv7";
"armv6l" = "arm";
"armv5tel" = "armv5te";
"riscv32" = "riscv32gc";
"riscv64" = "riscv64gc";
}
.${cpu.name} or cpu.name;
vendor_ = final.rust.platform.vendor;
# TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
in
args.rust.rustcTarget or args.rustc.config or (
# Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`.
# We cannot know which subversion does the user want, and
# currently use WASI 0.1 as default for compatibility. Custom
# users can set `rust.rustcTarget` to override it.
if final.isWasi then
"${cpu_}-wasip1"
else
"${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}"
);
# The name of the rust target if it is standard, or the json file
# containing the custom target spec.
rustcTargetSpec =
rust.rustcTargetSpec or (
if rust ? platform then
builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform)
else
final.rust.rustcTarget
);
# The name of the rust target if it is standard, or the
# basename of the file containing the custom target spec,
# without the .json extension.
#
# This is the name used by Cargo for target subdirectories.
cargoShortTarget = removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
# When used as part of an environment variable name, triples are
# uppercased and have all hyphens replaced by underscores:
#
# https://github.com/rust-lang/cargo/pull/9169
# https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
cargoEnvVarTarget = replaceStrings [ "-" ] [ "_" ] (toUpper final.rust.cargoShortTarget);
# True if the target is no_std
# https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
isNoStdTarget = any (t: hasInfix t final.rust.rustcTarget) [
"-none"
"nvptx"
"switch"
"-uefi"
];
};
}; };
}; in
in assert final.useAndroidPrebuilt -> final.isAndroid; assert final.useAndroidPrebuilt -> final.isAndroid;
assert foldl assert foldl (pass: { assertion, message }: if assertion final then pass else throw message) true (
(pass: { assertion, message }: final.parsed.abi.assertions or [ ]
if assertion final );
then pass
else throw message)
true
(final.parsed.abi.assertions or []);
final; final;
in in

View file

@ -7,16 +7,23 @@ let
all = [ all = [
# Cygwin # Cygwin
"i686-cygwin" "x86_64-cygwin" "i686-cygwin"
"x86_64-cygwin"
# Darwin # Darwin
"x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" "x86_64-darwin"
"i686-darwin"
"aarch64-darwin"
"armv7a-darwin"
# FreeBSD # FreeBSD
"i686-freebsd" "x86_64-freebsd" "i686-freebsd"
"x86_64-freebsd"
# Genode # Genode
"aarch64-genode" "i686-genode" "x86_64-genode" "aarch64-genode"
"i686-genode"
"x86_64-genode"
# illumos # illumos
"x86_64-solaris" "x86_64-solaris"
@ -25,94 +32,163 @@ let
"javascript-ghcjs" "javascript-ghcjs"
# Linux # Linux
"aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "aarch64-linux"
"armv7l-linux" "i686-linux" "loongarch64-linux" "m68k-linux" "microblaze-linux" "armv5tel-linux"
"microblazeel-linux" "mips-linux" "mips64-linux" "mips64el-linux" "armv6l-linux"
"mipsel-linux" "powerpc64-linux" "powerpc64le-linux" "riscv32-linux" "armv7a-linux"
"riscv64-linux" "s390-linux" "s390x-linux" "x86_64-linux" "armv7l-linux"
"i686-linux"
"loongarch64-linux"
"m68k-linux"
"microblaze-linux"
"microblazeel-linux"
"mips-linux"
"mips64-linux"
"mips64el-linux"
"mipsel-linux"
"powerpc64-linux"
"powerpc64le-linux"
"riscv32-linux"
"riscv64-linux"
"s390-linux"
"s390x-linux"
"x86_64-linux"
# MMIXware # MMIXware
"mmix-mmixware" "mmix-mmixware"
# NetBSD # NetBSD
"aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "aarch64-netbsd"
"i686-netbsd" "m68k-netbsd" "mipsel-netbsd" "powerpc-netbsd" "armv6l-netbsd"
"riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" "armv7a-netbsd"
"armv7l-netbsd"
"i686-netbsd"
"m68k-netbsd"
"mipsel-netbsd"
"powerpc-netbsd"
"riscv32-netbsd"
"riscv64-netbsd"
"x86_64-netbsd"
# none # none
"aarch64_be-none" "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-none" "aarch64_be-none"
"microblaze-none" "microblazeel-none" "mips-none" "mips64-none" "msp430-none" "or1k-none" "m68k-none" "aarch64-none"
"powerpc-none" "powerpcle-none" "riscv32-none" "riscv64-none" "rx-none" "arm-none"
"s390-none" "s390x-none" "vc4-none" "x86_64-none" "armv6l-none"
"avr-none"
"i686-none"
"microblaze-none"
"microblazeel-none"
"mips-none"
"mips64-none"
"msp430-none"
"or1k-none"
"m68k-none"
"powerpc-none"
"powerpcle-none"
"riscv32-none"
"riscv64-none"
"rx-none"
"s390-none"
"s390x-none"
"vc4-none"
"x86_64-none"
# OpenBSD # OpenBSD
"i686-openbsd" "x86_64-openbsd" "i686-openbsd"
"x86_64-openbsd"
# Redox # Redox
"x86_64-redox" "x86_64-redox"
# WASI # WASI
"wasm64-wasi" "wasm32-wasi" "wasm64-wasi"
"wasm32-wasi"
# Windows # Windows
"aarch64-windows" "x86_64-windows" "i686-windows" "aarch64-windows"
"x86_64-windows"
"i686-windows"
]; ];
allParsed = map parse.mkSystemFromString all; allParsed = map parse.mkSystemFromString all;
filterDoubles = f: map parse.doubleFromSystem (lists.filter f allParsed); filterDoubles = f: map parse.doubleFromSystem (lists.filter f allParsed);
in { in
{
inherit all; inherit all;
none = []; none = [ ];
arm = filterDoubles predicates.isAarch32; arm = filterDoubles predicates.isAarch32;
armv7 = filterDoubles predicates.isArmv7; armv7 = filterDoubles predicates.isArmv7;
aarch = filterDoubles predicates.isAarch; aarch = filterDoubles predicates.isAarch;
aarch64 = filterDoubles predicates.isAarch64; aarch64 = filterDoubles predicates.isAarch64;
x86 = filterDoubles predicates.isx86; x86 = filterDoubles predicates.isx86;
i686 = filterDoubles predicates.isi686; i686 = filterDoubles predicates.isi686;
x86_64 = filterDoubles predicates.isx86_64; x86_64 = filterDoubles predicates.isx86_64;
microblaze = filterDoubles predicates.isMicroBlaze; microblaze = filterDoubles predicates.isMicroBlaze;
mips = filterDoubles predicates.isMips; mips = filterDoubles predicates.isMips;
mmix = filterDoubles predicates.isMmix; mmix = filterDoubles predicates.isMmix;
power = filterDoubles predicates.isPower; power = filterDoubles predicates.isPower;
riscv = filterDoubles predicates.isRiscV; riscv = filterDoubles predicates.isRiscV;
riscv32 = filterDoubles predicates.isRiscV32; riscv32 = filterDoubles predicates.isRiscV32;
riscv64 = filterDoubles predicates.isRiscV64; riscv64 = filterDoubles predicates.isRiscV64;
rx = filterDoubles predicates.isRx; rx = filterDoubles predicates.isRx;
vc4 = filterDoubles predicates.isVc4; vc4 = filterDoubles predicates.isVc4;
or1k = filterDoubles predicates.isOr1k; or1k = filterDoubles predicates.isOr1k;
m68k = filterDoubles predicates.isM68k; m68k = filterDoubles predicates.isM68k;
s390 = filterDoubles predicates.isS390; s390 = filterDoubles predicates.isS390;
s390x = filterDoubles predicates.isS390x; s390x = filterDoubles predicates.isS390x;
loongarch64 = filterDoubles predicates.isLoongArch64; loongarch64 = filterDoubles predicates.isLoongArch64;
js = filterDoubles predicates.isJavaScript; js = filterDoubles predicates.isJavaScript;
bigEndian = filterDoubles predicates.isBigEndian; bigEndian = filterDoubles predicates.isBigEndian;
littleEndian = filterDoubles predicates.isLittleEndian; littleEndian = filterDoubles predicates.isLittleEndian;
cygwin = filterDoubles predicates.isCygwin; cygwin = filterDoubles predicates.isCygwin;
darwin = filterDoubles predicates.isDarwin; darwin = filterDoubles predicates.isDarwin;
freebsd = filterDoubles predicates.isFreeBSD; freebsd = filterDoubles predicates.isFreeBSD;
# Should be better, but MinGW is unclear. # Should be better, but MinGW is unclear.
gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) gnu =
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) filterDoubles (matchAttrs {
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; }) kernel = parse.kernels.linux;
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabin32; }) abi = parse.abis.gnu;
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabi64; }) })
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabielfv1; }) ++ filterDoubles (matchAttrs {
++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabielfv2; }); kernel = parse.kernels.linux;
illumos = filterDoubles predicates.isSunOS; abi = parse.abis.gnueabi;
linux = filterDoubles predicates.isLinux; })
netbsd = filterDoubles predicates.isNetBSD; ++ filterDoubles (matchAttrs {
openbsd = filterDoubles predicates.isOpenBSD; kernel = parse.kernels.linux;
unix = filterDoubles predicates.isUnix; abi = parse.abis.gnueabihf;
wasi = filterDoubles predicates.isWasi; })
redox = filterDoubles predicates.isRedox; ++ filterDoubles (matchAttrs {
windows = filterDoubles predicates.isWindows; kernel = parse.kernels.linux;
genode = filterDoubles predicates.isGenode; abi = parse.abis.gnuabin32;
})
++ filterDoubles (matchAttrs {
kernel = parse.kernels.linux;
abi = parse.abis.gnuabi64;
})
++ filterDoubles (matchAttrs {
kernel = parse.kernels.linux;
abi = parse.abis.gnuabielfv1;
})
++ filterDoubles (matchAttrs {
kernel = parse.kernels.linux;
abi = parse.abis.gnuabielfv2;
});
illumos = filterDoubles predicates.isSunOS;
linux = filterDoubles predicates.isLinux;
netbsd = filterDoubles predicates.isNetBSD;
openbsd = filterDoubles predicates.isOpenBSD;
unix = filterDoubles predicates.isUnix;
wasi = filterDoubles predicates.isWasi;
redox = filterDoubles predicates.isRedox;
windows = filterDoubles predicates.isWindows;
genode = filterDoubles predicates.isGenode;
embedded = filterDoubles predicates.isNone; embedded = filterDoubles predicates.isNone;
} }

View file

@ -26,7 +26,9 @@ rec {
}; };
ppc64-musl = { ppc64-musl = {
config = "powerpc64-unknown-linux-musl"; config = "powerpc64-unknown-linux-musl";
gcc = { abi = "elfv2"; }; gcc = {
abi = "elfv2";
};
}; };
sheevaplug = { sheevaplug = {
@ -95,16 +97,28 @@ rec {
} // platforms.fuloong2f_n32; } // platforms.fuloong2f_n32;
# can execute on 32bit chip # can execute on 32bit chip
mips-linux-gnu = { config = "mips-unknown-linux-gnu"; } // platforms.gcc_mips32r2_o32; mips-linux-gnu = {
mipsel-linux-gnu = { config = "mipsel-unknown-linux-gnu"; } // platforms.gcc_mips32r2_o32; config = "mips-unknown-linux-gnu";
} // platforms.gcc_mips32r2_o32;
mipsel-linux-gnu = {
config = "mipsel-unknown-linux-gnu";
} // platforms.gcc_mips32r2_o32;
# require 64bit chip (for more registers, 64-bit floating point, 64-bit "long long") but use 32bit pointers # require 64bit chip (for more registers, 64-bit floating point, 64-bit "long long") but use 32bit pointers
mips64-linux-gnuabin32 = { config = "mips64-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; mips64-linux-gnuabin32 = {
mips64el-linux-gnuabin32 = { config = "mips64el-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; config = "mips64-unknown-linux-gnuabin32";
} // platforms.gcc_mips64r2_n32;
mips64el-linux-gnuabin32 = {
config = "mips64el-unknown-linux-gnuabin32";
} // platforms.gcc_mips64r2_n32;
# 64bit pointers # 64bit pointers
mips64-linux-gnuabi64 = { config = "mips64-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; mips64-linux-gnuabi64 = {
mips64el-linux-gnuabi64 = { config = "mips64el-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; config = "mips64-unknown-linux-gnuabi64";
} // platforms.gcc_mips64r2_64;
mips64el-linux-gnuabi64 = {
config = "mips64el-unknown-linux-gnuabi64";
} // platforms.gcc_mips64r2_64;
muslpi = raspberryPi // { muslpi = raspberryPi // {
config = "armv6l-unknown-linux-musleabihf"; config = "armv6l-unknown-linux-musleabihf";
@ -114,12 +128,20 @@ rec {
config = "aarch64-unknown-linux-musl"; config = "aarch64-unknown-linux-musl";
}; };
gnu64 = { config = "x86_64-unknown-linux-gnu"; }; gnu64 = {
config = "x86_64-unknown-linux-gnu";
};
gnu64_simplekernel = gnu64 // platforms.pc_simplekernel; # see test/cross/default.nix gnu64_simplekernel = gnu64 // platforms.pc_simplekernel; # see test/cross/default.nix
gnu32 = { config = "i686-unknown-linux-gnu"; }; gnu32 = {
config = "i686-unknown-linux-gnu";
};
musl64 = { config = "x86_64-unknown-linux-musl"; }; musl64 = {
musl32 = { config = "i686-unknown-linux-musl"; }; config = "x86_64-unknown-linux-musl";
};
musl32 = {
config = "i686-unknown-linux-musl";
};
riscv64 = riscv "64"; riscv64 = riscv "64";
riscv32 = riscv "32"; riscv32 = riscv "32";
@ -294,13 +316,13 @@ rec {
aarch64-darwin = { aarch64-darwin = {
config = "aarch64-apple-darwin"; config = "aarch64-apple-darwin";
xcodePlatform = "MacOSX"; xcodePlatform = "MacOSX";
platform = {}; platform = { };
}; };
x86_64-darwin = { x86_64-darwin = {
config = "x86_64-apple-darwin"; config = "x86_64-apple-darwin";
xcodePlatform = "MacOSX"; xcodePlatform = "MacOSX";
platform = {}; platform = { };
}; };
# #

View file

@ -38,123 +38,428 @@ rec {
# `lib.attrsets.matchAttrs`, which requires a match on *all* attributes of # `lib.attrsets.matchAttrs`, which requires a match on *all* attributes of
# the product. # the product.
isi686 = { cpu = cpuTypes.i686; }; isi686 = {
isx86_32 = { cpu = { family = "x86"; bits = 32; }; }; cpu = cpuTypes.i686;
isx86_64 = { cpu = { family = "x86"; bits = 64; }; }; };
isPower = { cpu = { family = "power"; }; }; isx86_32 = {
isPower64 = { cpu = { family = "power"; bits = 64; }; }; cpu = {
family = "x86";
bits = 32;
};
};
isx86_64 = {
cpu = {
family = "x86";
bits = 64;
};
};
isPower = {
cpu = {
family = "power";
};
};
isPower64 = {
cpu = {
family = "power";
bits = 64;
};
};
# This ABI is the default in NixOS PowerPC64 BE, but not on mainline GCC, # This ABI is the default in NixOS PowerPC64 BE, but not on mainline GCC,
# so it sometimes causes issues in certain packages that makes the wrong # so it sometimes causes issues in certain packages that makes the wrong
# assumption on the used ABI. # assumption on the used ABI.
isAbiElfv2 = [ isAbiElfv2 = [
{ abi = { abi = "elfv2"; }; } {
{ abi = { name = "musl"; }; cpu = { family = "power"; bits = 64; }; } abi = {
abi = "elfv2";
};
}
{
abi = {
name = "musl";
};
cpu = {
family = "power";
bits = 64;
};
}
]; ];
isx86 = { cpu = { family = "x86"; }; }; isx86 = {
isAarch32 = { cpu = { family = "arm"; bits = 32; }; }; cpu = {
isArmv7 = map ({ arch, ... }: { cpu = { inherit arch; }; }) family = "x86";
(filter (cpu: hasPrefix "armv7" cpu.arch or "") };
(attrValues cpuTypes)); };
isAarch64 = { cpu = { family = "arm"; bits = 64; }; }; isAarch32 = {
isAarch = { cpu = { family = "arm"; }; }; cpu = {
isMicroBlaze = { cpu = { family = "microblaze"; }; }; family = "arm";
isMips = { cpu = { family = "mips"; }; }; bits = 32;
isMips32 = { cpu = { family = "mips"; bits = 32; }; }; };
isMips64 = { cpu = { family = "mips"; bits = 64; }; }; };
isMips64n32 = { cpu = { family = "mips"; bits = 64; }; abi = { abi = "n32"; }; }; isArmv7 = map (
isMips64n64 = { cpu = { family = "mips"; bits = 64; }; abi = { abi = "64"; }; }; { arch, ... }:
isMmix = { cpu = { family = "mmix"; }; }; {
isRiscV = { cpu = { family = "riscv"; }; }; cpu = { inherit arch; };
isRiscV32 = { cpu = { family = "riscv"; bits = 32; }; }; }
isRiscV64 = { cpu = { family = "riscv"; bits = 64; }; }; ) (filter (cpu: hasPrefix "armv7" cpu.arch or "") (attrValues cpuTypes));
isRx = { cpu = { family = "rx"; }; }; isAarch64 = {
isSparc = { cpu = { family = "sparc"; }; }; cpu = {
isSparc64 = { cpu = { family = "sparc"; bits = 64; }; }; family = "arm";
isWasm = { cpu = { family = "wasm"; }; }; bits = 64;
isMsp430 = { cpu = { family = "msp430"; }; }; };
isVc4 = { cpu = { family = "vc4"; }; }; };
isAvr = { cpu = { family = "avr"; }; }; isAarch = {
isAlpha = { cpu = { family = "alpha"; }; }; cpu = {
isOr1k = { cpu = { family = "or1k"; }; }; family = "arm";
isM68k = { cpu = { family = "m68k"; }; }; };
isS390 = { cpu = { family = "s390"; }; }; };
isS390x = { cpu = { family = "s390"; bits = 64; }; }; isMicroBlaze = {
isLoongArch64 = { cpu = { family = "loongarch"; bits = 64; }; }; cpu = {
isJavaScript = { cpu = cpuTypes.javascript; }; family = "microblaze";
};
};
isMips = {
cpu = {
family = "mips";
};
};
isMips32 = {
cpu = {
family = "mips";
bits = 32;
};
};
isMips64 = {
cpu = {
family = "mips";
bits = 64;
};
};
isMips64n32 = {
cpu = {
family = "mips";
bits = 64;
};
abi = {
abi = "n32";
};
};
isMips64n64 = {
cpu = {
family = "mips";
bits = 64;
};
abi = {
abi = "64";
};
};
isMmix = {
cpu = {
family = "mmix";
};
};
isRiscV = {
cpu = {
family = "riscv";
};
};
isRiscV32 = {
cpu = {
family = "riscv";
bits = 32;
};
};
isRiscV64 = {
cpu = {
family = "riscv";
bits = 64;
};
};
isRx = {
cpu = {
family = "rx";
};
};
isSparc = {
cpu = {
family = "sparc";
};
};
isSparc64 = {
cpu = {
family = "sparc";
bits = 64;
};
};
isWasm = {
cpu = {
family = "wasm";
};
};
isMsp430 = {
cpu = {
family = "msp430";
};
};
isVc4 = {
cpu = {
family = "vc4";
};
};
isAvr = {
cpu = {
family = "avr";
};
};
isAlpha = {
cpu = {
family = "alpha";
};
};
isOr1k = {
cpu = {
family = "or1k";
};
};
isM68k = {
cpu = {
family = "m68k";
};
};
isS390 = {
cpu = {
family = "s390";
};
};
isS390x = {
cpu = {
family = "s390";
bits = 64;
};
};
isLoongArch64 = {
cpu = {
family = "loongarch";
bits = 64;
};
};
isJavaScript = {
cpu = cpuTypes.javascript;
};
is32bit = { cpu = { bits = 32; }; }; is32bit = {
is64bit = { cpu = { bits = 64; }; }; cpu = {
isILP32 = [ { cpu = { family = "wasm"; bits = 32; }; } ] ++ bits = 32;
map (a: { abi = { abi = a; }; }) [ "n32" "ilp32" "x32" ]; };
isBigEndian = { cpu = { significantByte = significantBytes.bigEndian; }; }; };
isLittleEndian = { cpu = { significantByte = significantBytes.littleEndian; }; }; is64bit = {
cpu = {
bits = 64;
};
};
isILP32 =
[
{
cpu = {
family = "wasm";
bits = 32;
};
}
]
++ map
(a: {
abi = {
abi = a;
};
})
[
"n32"
"ilp32"
"x32"
];
isBigEndian = {
cpu = {
significantByte = significantBytes.bigEndian;
};
};
isLittleEndian = {
cpu = {
significantByte = significantBytes.littleEndian;
};
};
isBSD = { kernel = { families = { inherit (kernelFamilies) bsd; }; }; }; isBSD = {
isDarwin = { kernel = { families = { inherit (kernelFamilies) darwin; }; }; }; kernel = {
isUnix = [ isBSD isDarwin isLinux isSunOS isCygwin isRedox ]; families = { inherit (kernelFamilies) bsd; };
};
};
isDarwin = {
kernel = {
families = { inherit (kernelFamilies) darwin; };
};
};
isUnix = [
isBSD
isDarwin
isLinux
isSunOS
isCygwin
isRedox
];
isMacOS = { kernel = kernels.macos; }; isMacOS = {
isiOS = { kernel = kernels.ios; }; kernel = kernels.macos;
isLinux = { kernel = kernels.linux; }; };
isSunOS = { kernel = kernels.solaris; }; isiOS = {
isFreeBSD = { kernel = { name = "freebsd"; }; }; kernel = kernels.ios;
isNetBSD = { kernel = kernels.netbsd; }; };
isOpenBSD = { kernel = kernels.openbsd; }; isLinux = {
isWindows = { kernel = kernels.windows; }; kernel = kernels.linux;
isCygwin = { kernel = kernels.windows; abi = abis.cygnus; }; };
isMinGW = { kernel = kernels.windows; abi = abis.gnu; }; isSunOS = {
isWasi = { kernel = kernels.wasi; }; kernel = kernels.solaris;
isRedox = { kernel = kernels.redox; }; };
isGhcjs = { kernel = kernels.ghcjs; }; isFreeBSD = {
isGenode = { kernel = kernels.genode; }; kernel = {
isNone = { kernel = kernels.none; }; name = "freebsd";
};
};
isNetBSD = {
kernel = kernels.netbsd;
};
isOpenBSD = {
kernel = kernels.openbsd;
};
isWindows = {
kernel = kernels.windows;
};
isCygwin = {
kernel = kernels.windows;
abi = abis.cygnus;
};
isMinGW = {
kernel = kernels.windows;
abi = abis.gnu;
};
isWasi = {
kernel = kernels.wasi;
};
isRedox = {
kernel = kernels.redox;
};
isGhcjs = {
kernel = kernels.ghcjs;
};
isGenode = {
kernel = kernels.genode;
};
isNone = {
kernel = kernels.none;
};
isAndroid = [ { abi = abis.android; } { abi = abis.androideabi; } ]; isAndroid = [
isGnu = with abis; map (a: { abi = a; }) [ gnuabi64 gnuabin32 gnu gnueabi gnueabihf gnuabielfv1 gnuabielfv2 ]; { abi = abis.android; }
isMusl = with abis; map (a: { abi = a; }) [ musl musleabi musleabihf muslabin32 muslabi64 ]; { abi = abis.androideabi; }
isUClibc = with abis; map (a: { abi = a; }) [ uclibc uclibceabi uclibceabihf ]; ];
isGnu =
with abis;
map (a: { abi = a; }) [
gnuabi64
gnuabin32
gnu
gnueabi
gnueabihf
gnuabielfv1
gnuabielfv2
];
isMusl =
with abis;
map (a: { abi = a; }) [
musl
musleabi
musleabihf
muslabin32
muslabi64
];
isUClibc =
with abis;
map (a: { abi = a; }) [
uclibc
uclibceabi
uclibceabihf
];
isEfi = [ isEfi = [
{ cpu = { family = "arm"; version = "6"; }; } {
{ cpu = { family = "arm"; version = "7"; }; } cpu = {
{ cpu = { family = "arm"; version = "8"; }; } family = "arm";
{ cpu = { family = "riscv"; }; } version = "6";
{ cpu = { family = "x86"; }; } };
}
{
cpu = {
family = "arm";
version = "7";
};
}
{
cpu = {
family = "arm";
version = "8";
};
}
{
cpu = {
family = "riscv";
};
}
{
cpu = {
family = "x86";
};
}
]; ];
isElf = { kernel.execFormat = execFormats.elf; }; isElf = {
isMacho = { kernel.execFormat = execFormats.macho; }; kernel.execFormat = execFormats.elf;
};
isMacho = {
kernel.execFormat = execFormats.macho;
};
}; };
# given two patterns, return a pattern which is their logical AND. # given two patterns, return a pattern which is their logical AND.
# Since a pattern is a list-of-disjuncts, this needs to # Since a pattern is a list-of-disjuncts, this needs to
patternLogicalAnd = pat1_: pat2_: patternLogicalAnd =
pat1_: pat2_:
let let
# patterns can be either a list or a (bare) singleton; turn # patterns can be either a list or a (bare) singleton; turn
# them into singletons for uniform handling # them into singletons for uniform handling
pat1 = toList pat1_; pat1 = toList pat1_;
pat2 = toList pat2_; pat2 = toList pat2_;
in in
concatMap (attr1: concatMap (
map (attr2: attr1:
recursiveUpdateUntil map (
(path: subattr1: subattr2: attr2:
if (builtins.intersectAttrs subattr1 subattr2) == {} || subattr1 == subattr2 recursiveUpdateUntil (
then true path: subattr1: subattr2:
else throw '' if (builtins.intersectAttrs subattr1 subattr2) == { } || subattr1 == subattr2 then
pattern conflict at path ${toString path}: true
${toJSON subattr1} else
${toJSON subattr2} throw ''
'') pattern conflict at path ${toString path}:
attr1 ${toJSON subattr1}
attr2 ${toJSON subattr2}
) ''
pat2) ) attr1 attr2
pat1; ) pat2
) pat1;
matchAnyAttrs = patterns: matchAnyAttrs =
if isList patterns then attrs: any (pattern: matchAttrs pattern attrs) patterns patterns:
else matchAttrs patterns; if isList patterns then
attrs: any (pattern: matchAttrs pattern attrs) patterns
else
matchAttrs patterns;
predicates = mapAttrs (_: matchAnyAttrs) patterns; predicates = mapAttrs (_: matchAnyAttrs) patterns;
@ -163,7 +468,9 @@ rec {
# that `lib.meta.availableOn` can distinguish them from the patterns which # that `lib.meta.availableOn` can distinguish them from the patterns which
# apply only to the `parsed` field. # apply only to the `parsed` field.
platformPatterns = mapAttrs (_: p: { parsed = {}; } // p) { platformPatterns = mapAttrs (_: p: { parsed = { }; } // p) {
isStatic = { isStatic = true; }; isStatic = {
isStatic = true;
};
}; };
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,8 @@
{ lib, ... }: { lib, ... }:
let let
inherit (builtins) inherit (builtins)
storeDir; storeDir
;
inherit (lib) inherit (lib)
types types
mkOption mkOption

View file

@ -1,18 +1,24 @@
{ # The pkgs used for dependencies for the testing itself {
# The pkgs used for dependencies for the testing itself
# Don't test properties of pkgs.lib, but rather the lib in the parent directory # Don't test properties of pkgs.lib, but rather the lib in the parent directory
pkgs ? import ../.. {} // { lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; }, pkgs ? import ../.. { } // {
lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!";
},
nix ? pkgs-nixVersions.stable, nix ? pkgs-nixVersions.stable,
nixVersions ? [ pkgs-nixVersions.minimum nix pkgs-nixVersions.latest ], nixVersions ? [
pkgs-nixVersions.minimum
nix
pkgs-nixVersions.latest
],
pkgs-nixVersions ? import ./nix-for-tests.nix { inherit pkgs; }, pkgs-nixVersions ? import ./nix-for-tests.nix { inherit pkgs; },
}: }:
let let
lib = import ../.; lib = import ../.;
testWithNix = nix: testWithNix = nix: import ./test-with-nix.nix { inherit lib nix pkgs; };
import ./test-with-nix.nix { inherit lib nix pkgs; };
in in
pkgs.symlinkJoin { pkgs.symlinkJoin {
name = "nixpkgs-lib-tests"; name = "nixpkgs-lib-tests";
paths = map testWithNix nixVersions; paths = map testWithNix nixVersions;
} }

View file

@ -6,7 +6,7 @@
let let
lib = import ../default.nix; lib = import ../default.nix;
mseteq = x: y: { mseteq = x: y: {
expr = lib.sort lib.lessThan x; expr = lib.sort lib.lessThan x;
expected = lib.sort lib.lessThan y; expected = lib.sort lib.lessThan y;
}; };
@ -20,87 +20,248 @@ let
NOTE: This property is not guaranteed when `sys` was elaborated by a different NOTE: This property is not guaranteed when `sys` was elaborated by a different
version of Nixpkgs. version of Nixpkgs.
*/ */
toLosslessStringMaybe = sys: toLosslessStringMaybe =
if lib.isString sys then sys sys:
else if lib.systems.equals sys (lib.systems.elaborate sys.system) then sys.system if lib.isString sys then
else null; sys
else if lib.systems.equals sys (lib.systems.elaborate sys.system) then
sys.system
else
null;
in in
lib.runTests ( lib.runTests (
# We assert that the new algorithmic way of generating these lists matches the # We assert that the new algorithmic way of generating these lists matches the
# way they were hard-coded before. # way they were hard-coded before.
# #
# One might think "if we exhaustively test, what's the point of procedurally # One might think "if we exhaustively test, what's the point of procedurally
# calculating the lists anyway?". The answer is one can mindlessly update these # calculating the lists anyway?". The answer is one can mindlessly update these
# tests as new platforms become supported, and then just give the diff a quick # tests as new platforms become supported, and then just give the diff a quick
# sanity check before committing :). # sanity check before committing :).
(with lib.systems.doubles; { (with lib.systems.doubles; {
testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox); testall = mseteq all (
linux
++ darwin
++ freebsd
++ openbsd
++ netbsd
++ illumos
++ wasi
++ windows
++ embedded
++ mmix
++ js
++ genode
++ redox
);
testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ]; testarm = mseteq arm [
testarmv7 = mseteq armv7 [ "armv7a-darwin" "armv7a-linux" "armv7l-linux" "armv7a-netbsd" "armv7l-netbsd" ]; "armv5tel-linux"
testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ]; "armv6l-linux"
testmips = mseteq mips [ "mips-none" "mips64-none" "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "mipsel-netbsd" ]; "armv6l-netbsd"
testmmix = mseteq mmix [ "mmix-mmixware" ]; "armv6l-none"
testpower = mseteq power [ "powerpc-netbsd" "powerpc-none" "powerpc64-linux" "powerpc64le-linux" "powerpcle-none" ]; "armv7a-linux"
testriscv = mseteq riscv [ "riscv32-linux" "riscv64-linux" "riscv32-netbsd" "riscv64-netbsd" "riscv32-none" "riscv64-none" ]; "armv7a-netbsd"
testriscv32 = mseteq riscv32 [ "riscv32-linux" "riscv32-netbsd" "riscv32-none" ]; "armv7l-linux"
testriscv64 = mseteq riscv64 [ "riscv64-linux" "riscv64-netbsd" "riscv64-none" ]; "armv7l-netbsd"
tests390x = mseteq s390x [ "s390x-linux" "s390x-none" ]; "arm-none"
testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ]; "armv7a-darwin"
];
testarmv7 = mseteq armv7 [
"armv7a-darwin"
"armv7a-linux"
"armv7l-linux"
"armv7a-netbsd"
"armv7l-netbsd"
];
testi686 = mseteq i686 [
"i686-linux"
"i686-freebsd"
"i686-genode"
"i686-netbsd"
"i686-openbsd"
"i686-cygwin"
"i686-windows"
"i686-none"
"i686-darwin"
];
testmips = mseteq mips [
"mips-none"
"mips64-none"
"mips-linux"
"mips64-linux"
"mips64el-linux"
"mipsel-linux"
"mipsel-netbsd"
];
testmmix = mseteq mmix [ "mmix-mmixware" ];
testpower = mseteq power [
"powerpc-netbsd"
"powerpc-none"
"powerpc64-linux"
"powerpc64le-linux"
"powerpcle-none"
];
testriscv = mseteq riscv [
"riscv32-linux"
"riscv64-linux"
"riscv32-netbsd"
"riscv64-netbsd"
"riscv32-none"
"riscv64-none"
];
testriscv32 = mseteq riscv32 [
"riscv32-linux"
"riscv32-netbsd"
"riscv32-none"
];
testriscv64 = mseteq riscv64 [
"riscv64-linux"
"riscv64-netbsd"
"riscv64-none"
];
tests390x = mseteq s390x [
"s390x-linux"
"s390x-none"
];
testx86_64 = mseteq x86_64 [
"x86_64-linux"
"x86_64-darwin"
"x86_64-freebsd"
"x86_64-genode"
"x86_64-redox"
"x86_64-openbsd"
"x86_64-netbsd"
"x86_64-cygwin"
"x86_64-solaris"
"x86_64-windows"
"x86_64-none"
];
testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ]; testcygwin = mseteq cygwin [
testdarwin = mseteq darwin [ "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" ]; "i686-cygwin"
testfreebsd = mseteq freebsd [ "i686-freebsd" "x86_64-freebsd" ]; "x86_64-cygwin"
testgenode = mseteq genode [ "aarch64-genode" "i686-genode" "x86_64-genode" ]; ];
testredox = mseteq redox [ "x86_64-redox" ]; testdarwin = mseteq darwin [
testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */); "x86_64-darwin"
testillumos = mseteq illumos [ "x86_64-solaris" ]; "i686-darwin"
testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "loongarch64-linux" "m68k-linux" "microblaze-linux" "microblazeel-linux" "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "powerpc64-linux" "powerpc64le-linux" "riscv32-linux" "riscv64-linux" "s390-linux" "s390x-linux" "x86_64-linux" ]; "aarch64-darwin"
testnetbsd = mseteq netbsd [ "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "i686-netbsd" "m68k-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" ]; "armv7a-darwin"
testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ]; ];
testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "aarch64-windows" "i686-windows" "x86_64-windows" ]; testfreebsd = mseteq freebsd [
testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox); "i686-freebsd"
}) "x86_64-freebsd"
];
testgenode = mseteq genode [
"aarch64-genode"
"i686-genode"
"x86_64-genode"
];
testredox = mseteq redox [ "x86_64-redox" ];
testgnu = mseteq gnu (
linux # ++ kfreebsd ++ ...
);
testillumos = mseteq illumos [ "x86_64-solaris" ];
testlinux = mseteq linux [
"aarch64-linux"
"armv5tel-linux"
"armv6l-linux"
"armv7a-linux"
"armv7l-linux"
"i686-linux"
"loongarch64-linux"
"m68k-linux"
"microblaze-linux"
"microblazeel-linux"
"mips-linux"
"mips64-linux"
"mips64el-linux"
"mipsel-linux"
"powerpc64-linux"
"powerpc64le-linux"
"riscv32-linux"
"riscv64-linux"
"s390-linux"
"s390x-linux"
"x86_64-linux"
];
testnetbsd = mseteq netbsd [
"aarch64-netbsd"
"armv6l-netbsd"
"armv7a-netbsd"
"armv7l-netbsd"
"i686-netbsd"
"m68k-netbsd"
"mipsel-netbsd"
"powerpc-netbsd"
"riscv32-netbsd"
"riscv64-netbsd"
"x86_64-netbsd"
];
testopenbsd = mseteq openbsd [
"i686-openbsd"
"x86_64-openbsd"
];
testwindows = mseteq windows [
"i686-cygwin"
"x86_64-cygwin"
"aarch64-windows"
"i686-windows"
"x86_64-windows"
];
testunix = mseteq unix (
linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox
);
})
// { // {
test_equals_example_x86_64-linux = { test_equals_example_x86_64-linux = {
expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") (lib.systems.elaborate "x86_64-linux"); expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") (
expected = true; lib.systems.elaborate "x86_64-linux"
}; );
expected = true;
test_toLosslessStringMaybe_example_x86_64-linux = {
expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux");
expected = "x86_64-linux";
};
test_toLosslessStringMaybe_fail = {
expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux" // { something = "extra"; });
expected = null;
};
}
# Generate test cases to assert that a change in any non-function attribute makes a platform unequal
// lib.concatMapAttrs (platformAttrName: origValue: {
${"test_equals_unequal_${platformAttrName}"} =
let modified =
assert origValue != arbitraryValue;
lib.systems.elaborate "x86_64-linux" // { ${platformAttrName} = arbitraryValue; };
arbitraryValue = x: "<<modified>>";
in {
expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") modified;
expected = {
# Changes in these attrs are not detectable because they're function.
# The functions should be derived from the data, so this is not a problem.
canExecute = null;
emulator = null;
emulatorAvailable = null;
staticEmulatorAvailable = null;
isCompatible = null;
}?${platformAttrName};
}; };
}) (lib.systems.elaborate "x86_64-linux" /* arbitrary choice, just to get all the elaborated attrNames */) test_toLosslessStringMaybe_example_x86_64-linux = {
expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux");
expected = "x86_64-linux";
};
test_toLosslessStringMaybe_fail = {
expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux" // { something = "extra"; });
expected = null;
};
}
# Generate test cases to assert that a change in any non-function attribute makes a platform unequal
//
lib.concatMapAttrs
(platformAttrName: origValue: {
${"test_equals_unequal_${platformAttrName}"} =
let
modified =
assert origValue != arbitraryValue;
lib.systems.elaborate "x86_64-linux" // { ${platformAttrName} = arbitraryValue; };
arbitraryValue = x: "<<modified>>";
in
{
expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") modified;
expected =
{
# Changes in these attrs are not detectable because they're function.
# The functions should be derived from the data, so this is not a problem.
canExecute = null;
emulator = null;
emulatorAvailable = null;
staticEmulatorAvailable = null;
isCompatible = null;
} ? ${platformAttrName};
};
})
(
lib.systems.elaborate "x86_64-linux" # arbitrary choice, just to get all the elaborated attrNames
)
) )

View file

@ -11,11 +11,13 @@ let
toBaseDigits toBaseDigits
version version
versionSuffix versionSuffix
warn; warn
;
inherit (lib) inherit (lib)
isString isString
; ;
in { in
{
## Simple (higher order) functions ## Simple (higher order) functions
@ -23,7 +25,6 @@ in {
The identity function The identity function
For when you need a function that does nothing. For when you need a function that does nothing.
# Inputs # Inputs
`x` `x`
@ -44,7 +45,6 @@ in {
Ignores the second argument. If called with only one argument, Ignores the second argument. If called with only one argument,
constructs a function that always returns a static value. constructs a function that always returns a static value.
# Inputs # Inputs
`x` `x`
@ -72,9 +72,7 @@ in {
::: :::
*/ */
const = const = x: y: x;
x:
y: x;
/** /**
Pipes a value through a list of functions, left to right. Pipes a value through a list of functions, left to right.
@ -140,7 +138,6 @@ in {
/** /**
Concatenate two lists Concatenate two lists
# Inputs # Inputs
`x` `x`
@ -173,7 +170,6 @@ in {
/** /**
boolean or boolean or
# Inputs # Inputs
`x` `x`
@ -189,7 +185,6 @@ in {
/** /**
boolean and boolean and
# Inputs # Inputs
`x` `x`
@ -205,7 +200,6 @@ in {
/** /**
boolean exclusive or boolean exclusive or
# Inputs # Inputs
`x` `x`
@ -232,7 +226,6 @@ in {
boolean values. Calling `toString` on a bool instead returns "1" boolean values. Calling `toString` on a bool instead returns "1"
and "" (sic!). and "" (sic!).
# Inputs # Inputs
`b` `b`
@ -252,7 +245,6 @@ in {
mergeAttrs :: attrs -> attrs -> attrs mergeAttrs :: attrs -> attrs -> attrs
# Inputs # Inputs
`x` `x`
@ -263,7 +255,6 @@ in {
: Right attribute set (higher precedence for equal keys) : Right attribute set (higher precedence for equal keys)
# Examples # Examples
:::{.example} :::{.example}
## `lib.trivial.mergeAttrs` usage example ## `lib.trivial.mergeAttrs` usage example
@ -275,14 +266,11 @@ in {
::: :::
*/ */
mergeAttrs = mergeAttrs = x: y: x // y;
x:
y: x // y;
/** /**
Flip the order of the arguments of a binary function. Flip the order of the arguments of a binary function.
# Inputs # Inputs
`f` `f`
@ -314,12 +302,13 @@ in {
::: :::
*/ */
flip = f: a: b: f b a; flip =
f: a: b:
f b a;
/** /**
Return `maybeValue` if not null, otherwise return `default`. Return `maybeValue` if not null, otherwise return `default`.
# Inputs # Inputs
`default` `default`
@ -330,7 +319,6 @@ in {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.trivial.defaultTo` usage example ## `lib.trivial.defaultTo` usage example
@ -346,14 +334,11 @@ in {
::: :::
*/ */
defaultTo = default: maybeValue: defaultTo = default: maybeValue: if maybeValue != null then maybeValue else default;
if maybeValue != null then maybeValue
else default;
/** /**
Apply function if the supplied argument is non-null. Apply function if the supplied argument is non-null.
# Inputs # Inputs
`f` `f`
@ -364,7 +349,6 @@ in {
: Argument to check for null before passing it to `f` : Argument to check for null before passing it to `f`
# Examples # Examples
:::{.example} :::{.example}
## `lib.trivial.mapNullable` usage example ## `lib.trivial.mapNullable` usage example
@ -378,16 +362,25 @@ in {
::: :::
*/ */
mapNullable = mapNullable = f: a: if a == null then a else f a;
f:
a: if a == null then a else f a;
# Pull in some builtins not included elsewhere. # Pull in some builtins not included elsewhere.
inherit (builtins) inherit (builtins)
pathExists readFile isBool pathExists
isInt isFloat add sub lessThan readFile
seq deepSeq genericClosure isBool
bitAnd bitOr bitXor; isInt
isFloat
add
sub
lessThan
seq
deepSeq
genericClosure
bitAnd
bitOr
bitXor
;
## nixpkgs version strings ## nixpkgs version strings
@ -422,7 +415,6 @@ in {
Whether a feature is supported in all supported releases (at the time of Whether a feature is supported in all supported releases (at the time of
release branch-off, if applicable). See `oldestSupportedRelease`. release branch-off, if applicable). See `oldestSupportedRelease`.
# Inputs # Inputs
`release` `release`
@ -433,15 +425,13 @@ in {
isInOldestRelease = isInOldestRelease =
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2411) lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2411)
"lib.isInOldestRelease is deprecated. Use lib.oldestSupportedReleaseIsAtLeast instead." "lib.isInOldestRelease is deprecated. Use lib.oldestSupportedReleaseIsAtLeast instead."
lib.oldestSupportedReleaseIsAtLeast; lib.oldestSupportedReleaseIsAtLeast;
/** /**
Alias for `isInOldestRelease` introduced in 24.11. Alias for `isInOldestRelease` introduced in 24.11.
Use `isInOldestRelease` in expressions outside of Nixpkgs for greater compatibility. Use `isInOldestRelease` in expressions outside of Nixpkgs for greater compatibility.
*/ */
oldestSupportedReleaseIsAtLeast = oldestSupportedReleaseIsAtLeast = release: release <= lib.trivial.oldestSupportedRelease;
release:
release <= lib.trivial.oldestSupportedRelease;
/** /**
Returns the current nixpkgs release code name. Returns the current nixpkgs release code name.
@ -455,16 +445,15 @@ in {
Returns the current nixpkgs version suffix as string. Returns the current nixpkgs version suffix as string.
*/ */
versionSuffix = versionSuffix =
let suffixFile = ../.version-suffix; let
in if pathExists suffixFile suffixFile = ../.version-suffix;
then lib.strings.fileContents suffixFile in
else "pre-git"; if pathExists suffixFile then lib.strings.fileContents suffixFile else "pre-git";
/** /**
Attempts to return the the current revision of nixpkgs and Attempts to return the the current revision of nixpkgs and
returns the supplied default value otherwise. returns the supplied default value otherwise.
# Inputs # Inputs
`default` `default`
@ -481,11 +470,14 @@ in {
default: default:
let let
revisionFile = "${toString ./..}/.git-revision"; revisionFile = "${toString ./..}/.git-revision";
gitRepo = "${toString ./..}/.git"; gitRepo = "${toString ./..}/.git";
in if lib.pathIsGitRepo gitRepo in
then lib.commitIdFromGitRepo gitRepo if lib.pathIsGitRepo gitRepo then
else if lib.pathExists revisionFile then lib.fileContents revisionFile lib.commitIdFromGitRepo gitRepo
else default; else if lib.pathExists revisionFile then
lib.fileContents revisionFile
else
default;
nixpkgsVersion = warn "lib.nixpkgsVersion is a deprecated alias of lib.version." version; nixpkgsVersion = warn "lib.nixpkgsVersion is a deprecated alias of lib.version." version;
@ -512,14 +504,13 @@ in {
inPureEvalMode :: bool inPureEvalMode :: bool
``` ```
*/ */
inPureEvalMode = ! builtins ? currentSystem; inPureEvalMode = !builtins ? currentSystem;
## Integer operations ## Integer operations
/** /**
Return minimum of two numbers. Return minimum of two numbers.
# Inputs # Inputs
`x` `x`
@ -535,7 +526,6 @@ in {
/** /**
Return maximum of two numbers. Return maximum of two numbers.
# Inputs # Inputs
`x` `x`
@ -551,7 +541,6 @@ in {
/** /**
Integer modulus Integer modulus
# Inputs # Inputs
`base` `base`
@ -562,7 +551,6 @@ in {
: 2\. Function argument : 2\. Function argument
# Examples # Examples
:::{.example} :::{.example}
## `lib.trivial.mod` usage example ## `lib.trivial.mod` usage example
@ -578,7 +566,6 @@ in {
*/ */
mod = base: int: base - (int * (builtins.div base int)); mod = base: int: base - (int * (builtins.div base int));
## Comparisons ## Comparisons
/** /**
@ -588,7 +575,6 @@ in {
a == b, compare a b => 0 a == b, compare a b => 0
a > b, compare a b => 1 a > b, compare a b => 1
# Inputs # Inputs
`a` `a`
@ -599,12 +585,14 @@ in {
: 2\. Function argument : 2\. Function argument
*/ */
compare = a: b: compare =
if a < b a: b:
then -1 if a < b then
else if a > b -1
then 1 else if a > b then
else 0; 1
else
0;
/** /**
Split type into two subtypes by predicate `p`, take all elements Split type into two subtypes by predicate `p`, take all elements
@ -612,7 +600,6 @@ in {
second subtype, compare elements of a single subtype with `yes` second subtype, compare elements of a single subtype with `yes`
and `no` respectively. and `no` respectively.
# Inputs # Inputs
`p` `p`
@ -661,10 +648,12 @@ in {
*/ */
splitByAndCompare = splitByAndCompare =
p: yes: no: a: b: p: yes: no: a: b:
if p a if p a then
then if p b then yes a b else -1 if p b then yes a b else -1
else if p b then 1 else no a b; else if p b then
1
else
no a b;
/** /**
Reads a JSON file. Reads a JSON file.
@ -713,8 +702,7 @@ in {
importJSON :: path -> any importJSON :: path -> any
``` ```
*/ */
importJSON = path: importJSON = path: builtins.fromJSON (builtins.readFile path);
builtins.fromJSON (builtins.readFile path);
/** /**
Reads a TOML file. Reads a TOML file.
@ -761,11 +749,9 @@ in {
importTOML :: path -> any importTOML :: path -> any
``` ```
*/ */
importTOML = path: importTOML = path: builtins.fromTOML (builtins.readFile path);
builtins.fromTOML (builtins.readFile path);
/** /**
`warn` *`message`* *`value`* `warn` *`message`* *`value`*
Print a warning before returning the second argument. Print a warning before returning the second argument.
@ -792,19 +778,26 @@ in {
warn = warn =
# Since Nix 2.23, https://github.com/NixOS/nix/pull/10592 # Since Nix 2.23, https://github.com/NixOS/nix/pull/10592
builtins.warn or ( builtins.warn or (
let mustAbort = lib.elem (builtins.getEnv "NIX_ABORT_ON_WARN") ["1" "true" "yes"]; let
mustAbort = lib.elem (builtins.getEnv "NIX_ABORT_ON_WARN") [
"1"
"true"
"yes"
];
in in
# Do not eta reduce v, so that we have the same strictness as `builtins.warn`. # Do not eta reduce v, so that we have the same strictness as `builtins.warn`.
msg: v: msg: v:
# `builtins.warn` requires a string message, so we enforce that in our implementation, so that callers aren't accidentally incompatible with newer Nix versions. # `builtins.warn` requires a string message, so we enforce that in our implementation, so that callers aren't accidentally incompatible with newer Nix versions.
assert isString msg; assert isString msg;
if mustAbort if mustAbort then
then builtins.trace "evaluation warning: ${msg}" (abort "NIX_ABORT_ON_WARN=true; warnings are treated as unrecoverable errors.") builtins.trace "evaluation warning: ${msg}" (
else builtins.trace "evaluation warning: ${msg}" v abort "NIX_ABORT_ON_WARN=true; warnings are treated as unrecoverable errors."
)
else
builtins.trace "evaluation warning: ${msg}" v
); );
/** /**
`warnIf` *`condition`* *`message`* *`value`* `warnIf` *`condition`* *`message`* *`value`*
Like `warn`, but only warn when the first argument is `true`. Like `warn`, but only warn when the first argument is `true`.
@ -832,7 +825,6 @@ in {
warnIf = cond: msg: if cond then warn msg else x: x; warnIf = cond: msg: if cond then warn msg else x: x;
/** /**
`warnIfNot` *`condition`* *`message`* *`value`* `warnIfNot` *`condition`* *`message`* *`value`*
Like `warnIf`, but negated: warn if the first argument is `false`. Like `warnIf`, but negated: warn if the first argument is `false`.
@ -870,7 +862,6 @@ in {
Calls can be juxtaposed using function application, as `(r: r) a = a`, so Calls can be juxtaposed using function application, as `(r: r) a = a`, so
`(r: r) (r: r) a = a`, and so forth. `(r: r) (r: r) a = a`, and so forth.
# Inputs # Inputs
`cond` `cond`
@ -904,7 +895,6 @@ in {
/** /**
Like throwIfNot, but negated (throw if the first argument is `true`). Like throwIfNot, but negated (throw if the first argument is `true`).
# Inputs # Inputs
`cond` `cond`
@ -926,7 +916,6 @@ in {
/** /**
Check if the elements in a list are valid values from a enum, returning the identity function, or throwing an error message otherwise. Check if the elements in a list are valid values from a enum, returning the identity function, or throwing an error message otherwise.
# Inputs # Inputs
`msg` `msg`
@ -960,12 +949,13 @@ in {
::: :::
*/ */
checkListOfEnum = msg: valid: given: checkListOfEnum =
msg: valid: given:
let let
unexpected = lib.subtractLists valid given; unexpected = lib.subtractLists valid given;
in in
lib.throwIfNot (unexpected == []) lib.throwIfNot (unexpected == [ ])
"${msg}: ${builtins.concatStringsSep ", " (builtins.map builtins.toString unexpected)} unexpected; valid ones: ${builtins.concatStringsSep ", " (builtins.map builtins.toString valid)}"; "${msg}: ${builtins.concatStringsSep ", " (builtins.map builtins.toString unexpected)} unexpected; valid ones: ${builtins.concatStringsSep ", " (builtins.map builtins.toString valid)}";
info = msg: builtins.trace "INFO: ${msg}"; info = msg: builtins.trace "INFO: ${msg}";
@ -984,7 +974,6 @@ in {
function of the { a, b ? foo, ... }: format, but some facilities function of the { a, b ? foo, ... }: format, but some facilities
like callPackage expect to be able to query expected arguments. like callPackage expect to be able to query expected arguments.
# Inputs # Inputs
`f` `f`
@ -995,11 +984,11 @@ in {
: 2\. Function argument : 2\. Function argument
*/ */
setFunctionArgs = f: args: setFunctionArgs = f: args: {
{ # TODO: Should we add call-time "type" checking like built in? # TODO: Should we add call-time "type" checking like built in?
__functor = self: f; __functor = self: f;
__functionArgs = args; __functionArgs = args;
}; };
/** /**
Extract the expected function arguments from a function. Extract the expected function arguments from a function.
@ -1008,37 +997,35 @@ in {
has the same return type and semantics as builtins.functionArgs. has the same return type and semantics as builtins.functionArgs.
setFunctionArgs : (a b) Map String Bool. setFunctionArgs : (a b) Map String Bool.
# Inputs # Inputs
`f` `f`
: 1\. Function argument : 1\. Function argument
*/ */
functionArgs = f: functionArgs =
if f ? __functor f:
then f.__functionArgs or (functionArgs (f.__functor f)) if f ? __functor then
else builtins.functionArgs f; f.__functionArgs or (functionArgs (f.__functor f))
else
builtins.functionArgs f;
/** /**
Check whether something is a function or something Check whether something is a function or something
annotated with function args. annotated with function args.
# Inputs # Inputs
`f` `f`
: 1\. Function argument : 1\. Function argument
*/ */
isFunction = f: builtins.isFunction f || isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f));
(f ? __functor && isFunction (f.__functor f));
/** /**
`mirrorFunctionArgs f g` creates a new function `g'` with the same behavior as `g` (`g' x == g x`) `mirrorFunctionArgs f g` creates a new function `g'` with the same behavior as `g` (`g' x == g x`)
but its function arguments mirroring `f` (`lib.functionArgs g' == lib.functionArgs f`). but its function arguments mirroring `f` (`lib.functionArgs g' == lib.functionArgs f`).
# Inputs # Inputs
`f` `f`
@ -1084,21 +1071,18 @@ in {
let let
fArgs = functionArgs f; fArgs = functionArgs f;
in in
g: g: setFunctionArgs g fArgs;
setFunctionArgs g fArgs;
/** /**
Turns any non-callable values into constant functions. Turns any non-callable values into constant functions.
Returns callable values as is. Returns callable values as is.
# Inputs # Inputs
`v` `v`
: Any value : Any value
# Examples # Examples
:::{.example} :::{.example}
## `lib.trivial.toFunction` usage example ## `lib.trivial.toFunction` usage example
@ -1113,11 +1097,7 @@ in {
::: :::
*/ */
toFunction = toFunction = v: if isFunction v then v else k: v;
v:
if isFunction v
then v
else k: v;
/** /**
Convert a hexadecimal string to it's integer representation. Convert a hexadecimal string to it's integer representation.
@ -1138,12 +1118,15 @@ in {
=> 9223372036854775807 => 9223372036854775807
``` ```
*/ */
fromHexString = value: fromHexString =
let value:
noPrefix = lib.strings.removePrefix "0x" (lib.strings.toLower value); let
in let noPrefix = lib.strings.removePrefix "0x" (lib.strings.toLower value);
parsed = builtins.fromTOML "v=0x${noPrefix}"; in
in parsed.v; let
parsed = builtins.fromTOML "v=0x${noPrefix}";
in
parsed.v;
/** /**
Convert the given positive integer to a string of its hexadecimal Convert the given positive integer to a string of its hexadecimal
@ -1155,20 +1138,19 @@ in {
toHexString 250 => "FA" toHexString 250 => "FA"
*/ */
toHexString = let toHexString =
hexDigits = { let
"10" = "A"; hexDigits = {
"11" = "B"; "10" = "A";
"12" = "C"; "11" = "B";
"13" = "D"; "12" = "C";
"14" = "E"; "13" = "D";
"15" = "F"; "14" = "E";
}; "15" = "F";
toHexDigit = d: };
if d < 10 toHexDigit = d: if d < 10 then toString d else hexDigits.${toString d};
then toString d in
else hexDigits.${toString d}; i: lib.concatMapStrings toHexDigit (toBaseDigits 16 i);
in i: lib.concatMapStrings toHexDigit (toBaseDigits 16 i);
/** /**
`toBaseDigits base i` converts the positive integer i to a list of its `toBaseDigits base i` converts the positive integer i to a list of its
@ -1180,7 +1162,6 @@ in {
toBaseDigits 16 250 => [ 15 10 ] toBaseDigits 16 250 => [ 15 10 ]
# Inputs # Inputs
`base` `base`
@ -1191,21 +1172,23 @@ in {
: 2\. Function argument : 2\. Function argument
*/ */
toBaseDigits = base: i: toBaseDigits =
base: i:
let let
go = i: go =
if i < base i:
then [i] if i < base then
[ i ]
else else
let let
r = i - ((i / base) * base); r = i - ((i / base) * base);
q = (i - r) / base; q = (i - r) / base;
in in
[r] ++ go q; [ r ] ++ go q;
in in
assert (isInt base); assert (isInt base);
assert (isInt i); assert (isInt i);
assert (base >= 2); assert (base >= 2);
assert (i >= 0); assert (i >= 0);
lib.reverseList (go i); lib.reverseList (go i);
} }

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
{ pkgs {
, options pkgs,
, config options,
, version config,
, revision version,
, extraSources ? [] revision,
, baseOptionsJSON ? null extraSources ? [ ],
, warningsAreErrors ? true baseOptionsJSON ? null,
, prefix ? ../../.. warningsAreErrors ? true,
prefix ? ../../..,
}: }:
let let
@ -38,43 +39,61 @@ let
stripAnyPrefixes = flip (foldr removePrefix) prefixesToStrip; stripAnyPrefixes = flip (foldr removePrefix) prefixesToStrip;
optionsDoc = buildPackages.nixosOptionsDoc { optionsDoc = buildPackages.nixosOptionsDoc {
inherit options revision baseOptionsJSON warningsAreErrors; inherit
transformOptions = opt: opt // { options
# Clean up declaration sites to not refer to the NixOS source tree. revision
declarations = map stripAnyPrefixes opt.declarations; baseOptionsJSON
}; warningsAreErrors
;
transformOptions =
opt:
opt
// {
# Clean up declaration sites to not refer to the NixOS source tree.
declarations = map stripAnyPrefixes opt.declarations;
};
}; };
nixos-lib = import ../../lib { }; nixos-lib = import ../../lib { };
testOptionsDoc = let testOptionsDoc =
let
eval = nixos-lib.evalTest { eval = nixos-lib.evalTest {
# Avoid evaluating a NixOS config prototype. # Avoid evaluating a NixOS config prototype.
config.node.type = types.deferredModule; config.node.type = types.deferredModule;
options._module.args = mkOption { internal = true; }; options._module.args = mkOption { internal = true; };
}; };
in buildPackages.nixosOptionsDoc { in
buildPackages.nixosOptionsDoc {
inherit (eval) options; inherit (eval) options;
inherit revision; inherit revision;
transformOptions = opt: opt // { transformOptions =
# Clean up declaration sites to not refer to the NixOS source tree. opt:
declarations = opt
map // {
(decl: # Clean up declaration sites to not refer to the NixOS source tree.
if hasPrefix (toString ../../..) (toString decl) declarations = map (
then decl:
let subpath = removePrefix "/" (removePrefix (toString ../../..) (toString decl)); if hasPrefix (toString ../../..) (toString decl) then
in { url = "https://github.com/NixOS/nixpkgs/blob/master/${subpath}"; name = subpath; } let
else decl) subpath = removePrefix "/" (removePrefix (toString ../../..) (toString decl));
opt.declarations; in
}; {
url = "https://github.com/NixOS/nixpkgs/blob/master/${subpath}";
name = subpath;
}
else
decl
) opt.declarations;
};
documentType = "none"; documentType = "none";
variablelistId = "test-options-list"; variablelistId = "test-options-list";
optionIdPrefix = "test-opt-"; optionIdPrefix = "test-opt-";
}; };
testDriverMachineDocstrings = pkgs.callPackage testDriverMachineDocstrings =
../../../nixos/lib/test-driver/nixos-test-driver-docstrings.nix {}; pkgs.callPackage ../../../nixos/lib/test-driver/nixos-test-driver-docstrings.nix
{ };
prepareManualFromMD = '' prepareManualFromMD = ''
cp -r --no-preserve=all $inputs/* . cp -r --no-preserve=all $inputs/* .
@ -97,48 +116,51 @@ let
-i ./development/writing-nixos-tests.section.md -i ./development/writing-nixos-tests.section.md
''; '';
in rec { in
rec {
inherit (optionsDoc) optionsJSON optionsNix optionsDocBook; inherit (optionsDoc) optionsJSON optionsNix optionsDocBook;
# Generate the NixOS manual. # Generate the NixOS manual.
manualHTML = runCommand "nixos-manual-html" manualHTML =
{ nativeBuildInputs = [ buildPackages.nixos-render-docs ]; runCommand "nixos-manual-html"
inputs = sourceFilesBySuffices ./. [ ".md" ]; {
meta.description = "The NixOS manual in HTML format"; nativeBuildInputs = [ buildPackages.nixos-render-docs ];
allowedReferences = ["out"]; inputs = sourceFilesBySuffices ./. [ ".md" ];
} meta.description = "The NixOS manual in HTML format";
'' allowedReferences = [ "out" ];
# Generate the HTML manual. }
dst=$out/${common.outputPath} ''
mkdir -p $dst # Generate the HTML manual.
dst=$out/${common.outputPath}
mkdir -p $dst
cp ${../../../doc/style.css} $dst/style.css cp ${../../../doc/style.css} $dst/style.css
cp ${../../../doc/anchor.min.js} $dst/anchor.min.js cp ${../../../doc/anchor.min.js} $dst/anchor.min.js
cp ${../../../doc/anchor-use.js} $dst/anchor-use.js cp ${../../../doc/anchor-use.js} $dst/anchor-use.js
cp -r ${pkgs.documentation-highlighter} $dst/highlightjs cp -r ${pkgs.documentation-highlighter} $dst/highlightjs
${prepareManualFromMD} ${prepareManualFromMD}
nixos-render-docs -j $NIX_BUILD_CORES manual html \ nixos-render-docs -j $NIX_BUILD_CORES manual html \
--manpage-urls ${manpageUrls} \ --manpage-urls ${manpageUrls} \
--revision ${escapeShellArg revision} \ --revision ${escapeShellArg revision} \
--generator "nixos-render-docs ${pkgs.lib.version}" \ --generator "nixos-render-docs ${pkgs.lib.version}" \
--stylesheet style.css \ --stylesheet style.css \
--stylesheet highlightjs/mono-blue.css \ --stylesheet highlightjs/mono-blue.css \
--script ./highlightjs/highlight.pack.js \ --script ./highlightjs/highlight.pack.js \
--script ./highlightjs/loader.js \ --script ./highlightjs/loader.js \
--script ./anchor.min.js \ --script ./anchor.min.js \
--script ./anchor-use.js \ --script ./anchor-use.js \
--toc-depth 1 \ --toc-depth 1 \
--chunk-toc-depth 1 \ --chunk-toc-depth 1 \
./manual.md \ ./manual.md \
$dst/${common.indexPath} $dst/${common.indexPath}
mkdir -p $out/nix-support mkdir -p $out/nix-support
echo "nix-build out $out" >> $out/nix-support/hydra-build-products echo "nix-build out $out" >> $out/nix-support/hydra-build-products
echo "doc manual $dst" >> $out/nix-support/hydra-build-products echo "doc manual $dst" >> $out/nix-support/hydra-build-products
''; # */ ''; # */
# Alias for backward compatibility. TODO(@oxij): remove eventually. # Alias for backward compatibility. TODO(@oxij): remove eventually.
manual = manualHTML; manual = manualHTML;
@ -146,70 +168,77 @@ in rec {
# Index page of the NixOS manual. # Index page of the NixOS manual.
manualHTMLIndex = "${manualHTML}/${common.outputPath}/${common.indexPath}"; manualHTMLIndex = "${manualHTML}/${common.outputPath}/${common.indexPath}";
manualEpub = runCommand "nixos-manual-epub" manualEpub =
{ nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ]; runCommand "nixos-manual-epub"
doc = '' {
<book xmlns="http://docbook.org/ns/docbook" nativeBuildInputs = [
xmlns:xlink="http://www.w3.org/1999/xlink" buildPackages.libxml2.bin
version="5.0" buildPackages.libxslt.bin
xml:id="book-nixos-manual"> buildPackages.zip
<info> ];
<title>NixOS Manual</title> doc = ''
<subtitle>Version ${pkgs.lib.version}</subtitle> <book xmlns="http://docbook.org/ns/docbook"
</info> xmlns:xlink="http://www.w3.org/1999/xlink"
<chapter> version="5.0"
<title>Temporarily unavailable</title> xml:id="book-nixos-manual">
<para> <info>
The NixOS manual is currently not available in EPUB format, <title>NixOS Manual</title>
please use the <link xlink:href="https://nixos.org/nixos/manual">HTML manual</link> <subtitle>Version ${pkgs.lib.version}</subtitle>
instead. </info>
</para> <chapter>
<para> <title>Temporarily unavailable</title>
If you've used the EPUB manual in the past and it has been useful to you, please <para>
<link xlink:href="https://github.com/NixOS/nixpkgs/issues/237234">let us know</link>. The NixOS manual is currently not available in EPUB format,
</para> please use the <link xlink:href="https://nixos.org/nixos/manual">HTML manual</link>
</chapter> instead.
</book> </para>
<para>
If you've used the EPUB manual in the past and it has been useful to you, please
<link xlink:href="https://github.com/NixOS/nixpkgs/issues/237234">let us know</link>.
</para>
</chapter>
</book>
'';
passAsFile = [ "doc" ];
}
''
# Generate the epub manual.
dst=$out/${common.outputPath}
xsltproc \
--param chapter.autolabel 0 \
--nonet --xinclude --output $dst/epub/ \
${docbook_xsl_ns}/xml/xsl/docbook/epub/docbook.xsl \
$docPath
echo "application/epub+zip" > mimetype
manual="$dst/nixos-manual.epub"
zip -0Xq "$manual" mimetype
cd $dst/epub && zip -Xr9D "$manual" *
rm -rf $dst/epub
mkdir -p $out/nix-support
echo "doc-epub manual $manual" >> $out/nix-support/hydra-build-products
''; '';
passAsFile = [ "doc" ];
}
''
# Generate the epub manual.
dst=$out/${common.outputPath}
xsltproc \
--param chapter.autolabel 0 \
--nonet --xinclude --output $dst/epub/ \
${docbook_xsl_ns}/xml/xsl/docbook/epub/docbook.xsl \
$docPath
echo "application/epub+zip" > mimetype
manual="$dst/nixos-manual.epub"
zip -0Xq "$manual" mimetype
cd $dst/epub && zip -Xr9D "$manual" *
rm -rf $dst/epub
mkdir -p $out/nix-support
echo "doc-epub manual $manual" >> $out/nix-support/hydra-build-products
'';
# Generate the `man configuration.nix` package # Generate the `man configuration.nix` package
nixos-configuration-reference-manpage = runCommand "nixos-configuration-reference-manpage" nixos-configuration-reference-manpage =
{ nativeBuildInputs = [ runCommand "nixos-configuration-reference-manpage"
buildPackages.installShellFiles {
buildPackages.nixos-render-docs nativeBuildInputs = [
]; buildPackages.installShellFiles
allowedReferences = ["out"]; buildPackages.nixos-render-docs
} ];
'' allowedReferences = [ "out" ];
# Generate manpages. }
mkdir -p $out/share/man/man5 ''
nixos-render-docs -j $NIX_BUILD_CORES options manpage \ # Generate manpages.
--revision ${escapeShellArg revision} \ mkdir -p $out/share/man/man5
${optionsJSON}/${common.outputPath}/options.json \ nixos-render-docs -j $NIX_BUILD_CORES options manpage \
$out/share/man/man5/configuration.nix.5 --revision ${escapeShellArg revision} \
''; ${optionsJSON}/${common.outputPath}/options.json \
$out/share/man/man5/configuration.nix.5
'';
} }

View file

@ -8,40 +8,44 @@
# as subcomponents (e.g. the container feature, or nixops if network # as subcomponents (e.g. the container feature, or nixops if network
# expressions are ever made modular at the top level) can just use # expressions are ever made modular at the top level) can just use
# types.submodule instead of using eval-config.nix # types.submodule instead of using eval-config.nix
evalConfigArgs@ evalConfigArgs@{
{ # !!! system can be set modularly, would be nice to remove, # !!! system can be set modularly, would be nice to remove,
# however, removing or changing this default is too much # however, removing or changing this default is too much
# of a breaking change. To set it modularly, pass `null`. # of a breaking change. To set it modularly, pass `null`.
system ? builtins.currentSystem system ? builtins.currentSystem,
, # !!! is this argument needed any more? The pkgs argument can # !!! is this argument needed any more? The pkgs argument can
# be set modularly anyway. # be set modularly anyway.
pkgs ? null pkgs ? null,
, # !!! what do we gain by making this configurable? # !!! what do we gain by making this configurable?
# we can add modules that are included in specialisations, regardless # we can add modules that are included in specialisations, regardless
# of inheritParentConfig. # of inheritParentConfig.
baseModules ? import ../modules/module-list.nix baseModules ? import ../modules/module-list.nix,
, # !!! See comment about args in lib/modules.nix # !!! See comment about args in lib/modules.nix
extraArgs ? {} extraArgs ? { },
, # !!! See comment about args in lib/modules.nix # !!! See comment about args in lib/modules.nix
specialArgs ? {} specialArgs ? { },
, modules modules,
, modulesLocation ? (builtins.unsafeGetAttrPos "modules" evalConfigArgs).file or null modulesLocation ? (builtins.unsafeGetAttrPos "modules" evalConfigArgs).file or null,
, # !!! See comment about check in lib/modules.nix # !!! See comment about check in lib/modules.nix
check ? true check ? true,
, prefix ? [] prefix ? [ ],
, lib ? import ../../lib lib ? import ../../lib,
, extraModules ? let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH"; extraModules ?
in lib.optional (e != "") (import e) let
e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
in
lib.optional (e != "") (import e),
}: }:
let let
inherit (lib) optional; inherit (lib) optional;
evalModulesMinimal = (import ./default.nix { evalModulesMinimal =
inherit lib; (import ./default.nix {
# Implicit use of feature is noted in implementation. inherit lib;
featureFlags.minimalModules = { }; # Implicit use of feature is noted in implementation.
}).evalModules; featureFlags.minimalModules = { };
}).evalModules;
pkgsModule = rec { pkgsModule = rec {
_file = ./eval-config.nix; _file = ./eval-config.nix;
@ -54,26 +58,29 @@ let
# they way through, but has the last priority behind everything else. # they way through, but has the last priority behind everything else.
nixpkgs.system = lib.mkDefault system; nixpkgs.system = lib.mkDefault system;
}) })
++ ++ (optional (pkgs != null) {
(optional (pkgs != null) {
# This should be default priority, so it conflicts with any user-defined pkgs. # This should be default priority, so it conflicts with any user-defined pkgs.
nixpkgs.pkgs = pkgs; nixpkgs.pkgs = pkgs;
}) })
); );
}; };
withWarnings = x: withWarnings =
lib.warnIf (evalConfigArgs?extraArgs) "The extraArgs argument to eval-config.nix is deprecated. Please set config._module.args instead." x:
lib.warnIf (evalConfigArgs?check) "The check argument to eval-config.nix is deprecated. Please set config._module.check instead." lib.warnIf (evalConfigArgs ? extraArgs)
x; "The extraArgs argument to eval-config.nix is deprecated. Please set config._module.args instead."
lib.warnIf
(evalConfigArgs ? check)
"The check argument to eval-config.nix is deprecated. Please set config._module.check instead."
x;
legacyModules = legacyModules =
lib.optional (evalConfigArgs?extraArgs) { lib.optional (evalConfigArgs ? extraArgs) {
config = { config = {
_module.args = extraArgs; _module.args = extraArgs;
}; };
} }
++ lib.optional (evalConfigArgs?check) { ++ lib.optional (evalConfigArgs ? check) {
config = { config = {
_module.check = lib.mkDefault check; _module.check = lib.mkDefault check;
}; };
@ -89,29 +96,43 @@ let
else else
map (lib.setDefaultModuleLocation modulesLocation) modules; map (lib.setDefaultModuleLocation modulesLocation) modules;
in in
locatedModules ++ legacyModules; locatedModules ++ legacyModules;
noUserModules = evalModulesMinimal ({ noUserModules = evalModulesMinimal ({
inherit prefix specialArgs; inherit prefix specialArgs;
modules = baseModules ++ extraModules ++ [ pkgsModule modulesModule ]; modules =
baseModules
++ extraModules
++ [
pkgsModule
modulesModule
];
}); });
# Extra arguments that are useful for constructing a similar configuration. # Extra arguments that are useful for constructing a similar configuration.
modulesModule = { modulesModule = {
config = { config = {
_module.args = { _module.args = {
inherit noUserModules baseModules extraModules modules; inherit
noUserModules
baseModules
extraModules
modules
;
}; };
}; };
}; };
nixosWithUserModules = noUserModules.extendModules { modules = allUserModules; }; nixosWithUserModules = noUserModules.extendModules { modules = allUserModules; };
withExtraAttrs = configuration: configuration // { withExtraAttrs =
inherit extraArgs; configuration:
inherit (configuration._module.args) pkgs; configuration
inherit lib; // {
extendModules = args: withExtraAttrs (configuration.extendModules args); inherit extraArgs;
}; inherit (configuration._module.args) pkgs;
inherit lib;
extendModules = args: withExtraAttrs (configuration.extendModules args);
};
in in
withWarnings (withExtraAttrs nixosWithUserModules) withWarnings (withExtraAttrs nixosWithUserModules)

View file

@ -3,20 +3,22 @@
# contents of a directory that can be populated with commands. The # contents of a directory that can be populated with commands. The
# generated image is sized to only fit its contents, with the expectation # generated image is sized to only fit its contents, with the expectation
# that a script resizes the filesystem at boot time. # that a script resizes the filesystem at boot time.
{ pkgs {
, lib pkgs,
# List of derivations to be included lib,
, storePaths # List of derivations to be included
# Whether or not to compress the resulting image with zstd storePaths,
, compressImage ? false, zstd # Whether or not to compress the resulting image with zstd
# Shell commands to populate the ./files directory. compressImage ? false,
# All files in that directory are copied to the root of the FS. zstd,
, populateImageCommands ? "" # Shell commands to populate the ./files directory.
, volumeLabel # All files in that directory are copied to the root of the FS.
, uuid ? "44444444-4444-4444-8888-888888888888" populateImageCommands ? "",
, btrfs-progs volumeLabel,
, libfaketime uuid ? "44444444-4444-4444-8888-888888888888",
, fakeroot btrfs-progs,
libfaketime,
fakeroot,
}: }:
let let
@ -25,43 +27,46 @@ in
pkgs.stdenv.mkDerivation { pkgs.stdenv.mkDerivation {
name = "btrfs-fs.img${lib.optionalString compressImage ".zst"}"; name = "btrfs-fs.img${lib.optionalString compressImage ".zst"}";
nativeBuildInputs = [ btrfs-progs libfaketime fakeroot ] ++ lib.optional compressImage zstd; nativeBuildInputs = [
btrfs-progs
libfaketime
fakeroot
] ++ lib.optional compressImage zstd;
buildCommand = buildCommand = ''
'' ${if compressImage then "img=temp.img" else "img=$out"}
${if compressImage then "img=temp.img" else "img=$out"}
set -x set -x
( (
mkdir -p ./files mkdir -p ./files
${populateImageCommands} ${populateImageCommands}
) )
mkdir -p ./rootImage/nix/store mkdir -p ./rootImage/nix/store
xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths
( (
GLOBIGNORE=".:.." GLOBIGNORE=".:.."
shopt -u dotglob shopt -u dotglob
for f in ./files/*; do for f in ./files/*; do
cp -a --reflink=auto -t ./rootImage/ "$f" cp -a --reflink=auto -t ./rootImage/ "$f"
done done
) )
cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration
touch $img touch $img
faketime -f "1970-01-01 00:00:01" fakeroot mkfs.btrfs -L ${volumeLabel} -U ${uuid} -r ./rootImage --shrink $img faketime -f "1970-01-01 00:00:01" fakeroot mkfs.btrfs -L ${volumeLabel} -U ${uuid} -r ./rootImage --shrink $img
if ! btrfs check $img; then if ! btrfs check $img; then
echo "--- 'btrfs check' failed for BTRFS image ---" echo "--- 'btrfs check' failed for BTRFS image ---"
return 1 return 1
fi fi
if [ ${builtins.toString compressImage} ]; then if [ ${builtins.toString compressImage} ]; then
echo "Compressing image" echo "Compressing image"
zstd -v --no-progress ./$img -o $out zstd -v --no-progress ./$img -o $out
fi fi
''; '';
} }

View file

@ -1,115 +1,117 @@
/* Technical details /*
Technical details
`make-disk-image` has a bit of magic to minimize the amount of work to do in a virtual machine. `make-disk-image` has a bit of magic to minimize the amount of work to do in a virtual machine.
It relies on the [LKL (Linux Kernel Library) project](https://github.com/lkl/linux) which provides Linux kernel as userspace library. It relies on the [LKL (Linux Kernel Library) project](https://github.com/lkl/linux) which provides Linux kernel as userspace library.
The Nix-store only image only need to run LKL tools to produce an image and will never spawn a virtual machine, whereas full images will always require a virtual machine, but also use LKL. The Nix-store only image only need to run LKL tools to produce an image and will never spawn a virtual machine, whereas full images will always require a virtual machine, but also use LKL.
### Image preparation phase ### Image preparation phase
Image preparation phase will produce the initial image layout in a folder: Image preparation phase will produce the initial image layout in a folder:
- devise a root folder based on `$PWD` - devise a root folder based on `$PWD`
- prepare the contents by copying and restoring ACLs in this root folder - prepare the contents by copying and restoring ACLs in this root folder
- load in the Nix store database all additional paths computed by `pkgs.closureInfo` in a temporary Nix store - load in the Nix store database all additional paths computed by `pkgs.closureInfo` in a temporary Nix store
- run `nixos-install` in a temporary folder - run `nixos-install` in a temporary folder
- transfer from the temporary store the additional paths registered to the installed NixOS - transfer from the temporary store the additional paths registered to the installed NixOS
- compute the size of the disk image based on the apparent size of the root folder - compute the size of the disk image based on the apparent size of the root folder
- partition the disk image using the corresponding script according to the partition table type - partition the disk image using the corresponding script according to the partition table type
- format the partitions if needed - format the partitions if needed
- use `cptofs` (LKL tool) to copy the root folder inside the disk image - use `cptofs` (LKL tool) to copy the root folder inside the disk image
At this step, the disk image already contains the Nix store, it now only needs to be converted to the desired format to be used. At this step, the disk image already contains the Nix store, it now only needs to be converted to the desired format to be used.
### Image conversion phase ### Image conversion phase
Using `qemu-img`, the disk image is converted from a raw format to the desired format: qcow2(-compressed), vdi, vpc. Using `qemu-img`, the disk image is converted from a raw format to the desired format: qcow2(-compressed), vdi, vpc.
### Image Partitioning ### Image Partitioning
#### `none` #### `none`
No partition table layout is written. The image is a bare filesystem image. No partition table layout is written. The image is a bare filesystem image.
#### `legacy` #### `legacy`
The image is partitioned using MBR. There is one primary ext4 partition starting at 1 MiB that fills the rest of the disk image. The image is partitioned using MBR. There is one primary ext4 partition starting at 1 MiB that fills the rest of the disk image.
This partition layout is unsuitable for UEFI. This partition layout is unsuitable for UEFI.
#### `legacy+gpt` #### `legacy+gpt`
This partition table type uses GPT and: This partition table type uses GPT and:
- create a "no filesystem" partition from 1MiB to 2MiB ; - create a "no filesystem" partition from 1MiB to 2MiB ;
- set `bios_grub` flag on this "no filesystem" partition, which marks it as a [GRUB BIOS partition](https://www.gnu.org/software/parted/manual/html_node/set.html) ; - set `bios_grub` flag on this "no filesystem" partition, which marks it as a [GRUB BIOS partition](https://www.gnu.org/software/parted/manual/html_node/set.html) ;
- create a primary ext4 partition starting at 2MiB and extending to the full disk image ; - create a primary ext4 partition starting at 2MiB and extending to the full disk image ;
- perform optimal alignments checks on each partition - perform optimal alignments checks on each partition
This partition layout is unsuitable for UEFI boot, because it has no ESP (EFI System Partition) partition. It can work with CSM (Compatibility Support Module) which emulates legacy (BIOS) boot for UEFI. This partition layout is unsuitable for UEFI boot, because it has no ESP (EFI System Partition) partition. It can work with CSM (Compatibility Support Module) which emulates legacy (BIOS) boot for UEFI.
#### `efi` #### `efi`
This partition table type uses GPT and: This partition table type uses GPT and:
- creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ; - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
- creates an primary ext4 partition starting after the boot partition and extending to the full disk image - creates an primary ext4 partition starting after the boot partition and extending to the full disk image
#### `efixbootldr` #### `efixbootldr`
This partition table type uses GPT and: This partition table type uses GPT and:
- creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ; - creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ;
- creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ; - creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ;
- creates an primary ext4 partition starting after the boot partition and extending to the full disk image - creates an primary ext4 partition starting after the boot partition and extending to the full disk image
#### `hybrid` #### `hybrid`
This partition table type uses GPT and: This partition table type uses GPT and:
- creates a "no filesystem" partition from 0 to 1MiB, set `bios_grub` flag on it ; - creates a "no filesystem" partition from 0 to 1MiB, set `bios_grub` flag on it ;
- creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ; - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
- creates a primary ext4 partition starting after the boot one and extending to the full disk image - creates a primary ext4 partition starting after the boot one and extending to the full disk image
This partition could be booted by a BIOS able to understand GPT layouts and recognizing the MBR at the start. This partition could be booted by a BIOS able to understand GPT layouts and recognizing the MBR at the start.
### How to run determinism analysis on results? ### How to run determinism analysis on results?
Build your derivation with `--check` to rebuild it and verify it is the same. Build your derivation with `--check` to rebuild it and verify it is the same.
If it fails, you will be left with two folders with one having `.check`. If it fails, you will be left with two folders with one having `.check`.
You can use `diffoscope` to see the differences between the folders. You can use `diffoscope` to see the differences between the folders.
However, `diffoscope` is currently not able to diff two QCOW2 filesystems, thus, it is advised to use raw format. However, `diffoscope` is currently not able to diff two QCOW2 filesystems, thus, it is advised to use raw format.
Even if you use raw disks, `diffoscope` cannot diff the partition table and partitions recursively. Even if you use raw disks, `diffoscope` cannot diff the partition table and partitions recursively.
To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$image-p$i.raw skip=$start count=$sectors` for each `(start, sectors)` listed in the `fdisk` output. Now, you will have each partition as a separate file and you can compare them in pairs. To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$image-p$i.raw skip=$start count=$sectors` for each `(start, sectors)` listed in the `fdisk` output. Now, you will have each partition as a separate file and you can compare them in pairs.
*/ */
{ pkgs {
, lib pkgs,
lib,
, # The NixOS configuration to be installed onto the disk image. # The NixOS configuration to be installed onto the disk image.
config config,
, # The size of the disk, in megabytes. # The size of the disk, in megabytes.
# if "auto" size is calculated based on the contents copied to it and # if "auto" size is calculated based on the contents copied to it and
# additionalSpace is taken into account. # additionalSpace is taken into account.
diskSize ? "auto" diskSize ? "auto",
, # additional disk space to be added to the image if diskSize "auto" # additional disk space to be added to the image if diskSize "auto"
# is used # is used
additionalSpace ? "512M" additionalSpace ? "512M",
, # size of the boot partition, is only used if partitionTableType is # size of the boot partition, is only used if partitionTableType is
# either "efi" or "hybrid" # either "efi" or "hybrid"
# This will be undersized slightly, as this is actually the offset of # This will be undersized slightly, as this is actually the offset of
# the end of the partition. Generally it will be 1MiB smaller. # the end of the partition. Generally it will be 1MiB smaller.
bootSize ? "256M" bootSize ? "256M",
, # The files and directories to be placed in the target file system. # The files and directories to be placed in the target file system.
# This is a list of attribute sets {source, target, mode, user, group} where # This is a list of attribute sets {source, target, mode, user, group} where
# `source' is the file system object (regular file or directory) to be # `source' is the file system object (regular file or directory) to be
# grafted in the file system at path `target', `mode' is a string containing # grafted in the file system at path `target', `mode' is a string containing
@ -117,51 +119,51 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
# user and group name that will be set as owner of the files. # user and group name that will be set as owner of the files.
# `mode', `user', and `group' are optional. # `mode', `user', and `group' are optional.
# When setting one of `user' or `group', the other needs to be set too. # When setting one of `user' or `group', the other needs to be set too.
contents ? [] contents ? [ ],
, # Type of partition table to use; described in the `Image Partitioning` section above. # Type of partition table to use; described in the `Image Partitioning` section above.
partitionTableType ? "legacy" partitionTableType ? "legacy",
, # Whether to invoke `switch-to-configuration boot` during image creation # Whether to invoke `switch-to-configuration boot` during image creation
installBootLoader ? true installBootLoader ? true,
, # Whether to output have EFIVARS available in $out/efi-vars.fd and use it during disk creation # Whether to output have EFIVARS available in $out/efi-vars.fd and use it during disk creation
touchEFIVars ? false touchEFIVars ? false,
, # OVMF firmware derivation # OVMF firmware derivation
OVMF ? pkgs.OVMF.fd OVMF ? pkgs.OVMF.fd,
, # EFI firmware # EFI firmware
efiFirmware ? OVMF.firmware efiFirmware ? OVMF.firmware,
, # EFI variables # EFI variables
efiVariables ? OVMF.variables efiVariables ? OVMF.variables,
, # The root file system type. # The root file system type.
fsType ? "ext4" fsType ? "ext4",
, # Filesystem label # Filesystem label
label ? if onlyNixStore then "nix-store" else "nixos" label ? if onlyNixStore then "nix-store" else "nixos",
, # The initial NixOS configuration file to be copied to # The initial NixOS configuration file to be copied to
# /etc/nixos/configuration.nix. # /etc/nixos/configuration.nix.
configFile ? null configFile ? null,
, # Shell code executed after the VM has finished. # Shell code executed after the VM has finished.
postVM ? "" postVM ? "",
, # Guest memory size # Guest memory size
memSize ? 1024 memSize ? 1024,
, # Copy the contents of the Nix store to the root of the image and # Copy the contents of the Nix store to the root of the image and
# skip further setup. Incompatible with `contents`, # skip further setup. Incompatible with `contents`,
# `installBootLoader` and `configFile`. # `installBootLoader` and `configFile`.
onlyNixStore ? false onlyNixStore ? false,
, name ? "nixos-disk-image" name ? "nixos-disk-image",
, # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw. # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
format ? "raw" format ? "raw",
# Whether to fix: # Whether to fix:
# - GPT Disk Unique Identifier (diskGUID) # - GPT Disk Unique Identifier (diskGUID)
@ -171,139 +173,181 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
# BIOS/MBR support is "best effort" at the moment. # BIOS/MBR support is "best effort" at the moment.
# Boot partitions may not be deterministic. # Boot partitions may not be deterministic.
# Also, to fix last time checked of the ext4 partition if fsType = ext4. # Also, to fix last time checked of the ext4 partition if fsType = ext4.
, deterministic ? true deterministic ? true,
# GPT Partition Unique Identifier for root partition. # GPT Partition Unique Identifier for root partition.
, rootGPUID ? "F222513B-DED1-49FA-B591-20CE86A2FE7F" rootGPUID ? "F222513B-DED1-49FA-B591-20CE86A2FE7F",
# When fsType = ext4, this is the root Filesystem Unique Identifier. # When fsType = ext4, this is the root Filesystem Unique Identifier.
# TODO: support other filesystems someday. # TODO: support other filesystems someday.
, rootFSUID ? (if fsType == "ext4" then rootGPUID else null) rootFSUID ? (if fsType == "ext4" then rootGPUID else null),
, # Whether a nix channel based on the current source tree should be # Whether a nix channel based on the current source tree should be
# made available inside the image. Useful for interactive use of nix # made available inside the image. Useful for interactive use of nix
# utils, but changes the hash of the image when the sources are # utils, but changes the hash of the image when the sources are
# updated. # updated.
copyChannel ? true copyChannel ? true,
, # Additional store paths to copy to the image's store. # Additional store paths to copy to the image's store.
additionalPaths ? [] additionalPaths ? [ ],
}: }:
assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "efixbootldr" "hybrid" "none" ]); assert (
assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID."); lib.assertOneOf "partitionTableType" partitionTableType [
# We use -E offset=X below, which is only supported by e2fsprogs "legacy"
assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4"); "legacy+gpt"
assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt."); "efi"
# If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader. "efixbootldr"
assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed."); "hybrid"
"none"
]
);
assert (
lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null)
"In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID."
);
# We use -E offset=X below, which is only supported by e2fsprogs
assert (
lib.assertMsg (partitionTableType != "none" -> fsType == "ext4")
"to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4"
);
assert (
lib.assertMsg
(
touchEFIVars
->
partitionTableType == "hybrid"
|| partitionTableType == "efi"
|| partitionTableType == "efixbootldr"
|| partitionTableType == "legacy+gpt"
)
"EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt."
);
# If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader.
assert (
lib.assertMsg (onlyNixStore -> contents == [ ] && configFile == null && !installBootLoader)
"In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed."
);
# Either both or none of {user,group} need to be set # Either both or none of {user,group} need to be set
assert (lib.assertMsg (lib.all assert (
(attrs: ((attrs.user or null) == null) lib.assertMsg (lib.all (
== ((attrs.group or null) == null)) attrs: ((attrs.user or null) == null) == ((attrs.group or null) == null)
contents) "Contents of the disk image should set none of {user, group} or both at the same time."); ) contents) "Contents of the disk image should set none of {user, group} or both at the same time."
);
let format' = format; in let let
format' = format;
in
let
format = if format' == "qcow2-compressed" then "qcow2" else format'; format = if format' == "qcow2-compressed" then "qcow2" else format';
compress = lib.optionalString (format' == "qcow2-compressed") "-c"; compress = lib.optionalString (format' == "qcow2-compressed") "-c";
filename = "nixos." + { filename =
qcow2 = "qcow2"; "nixos."
vdi = "vdi"; + {
vpc = "vhd"; qcow2 = "qcow2";
raw = "img"; vdi = "vdi";
}.${format} or format; vpc = "vhd";
raw = "img";
}
.${format} or format;
rootPartition = { # switch-case rootPartition =
legacy = "1"; {
"legacy+gpt" = "2"; # switch-case
efi = "2"; legacy = "1";
efixbootldr = "3"; "legacy+gpt" = "2";
hybrid = "3"; efi = "2";
}.${partitionTableType}; efixbootldr = "3";
hybrid = "3";
}
.${partitionTableType};
partitionDiskScript = { # switch-case partitionDiskScript =
legacy = '' {
parted --script $diskImage -- \ # switch-case
mklabel msdos \ legacy = ''
mkpart primary ext4 1MiB -1 parted --script $diskImage -- \
''; mklabel msdos \
"legacy+gpt" = '' mkpart primary ext4 1MiB -1
parted --script $diskImage -- \ '';
mklabel gpt \ "legacy+gpt" = ''
mkpart no-fs 1MB 2MB \ parted --script $diskImage -- \
set 1 bios_grub on \ mklabel gpt \
align-check optimal 1 \ mkpart no-fs 1MB 2MB \
mkpart primary ext4 2MB -1 \ set 1 bios_grub on \
align-check optimal 2 \ align-check optimal 1 \
print mkpart primary ext4 2MB -1 \
${lib.optionalString deterministic '' align-check optimal 2 \
print
${lib.optionalString deterministic ''
sgdisk \ sgdisk \
--disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \ --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
--partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \ --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
--partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \ --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
--partition-guid=3:${rootGPUID} \ --partition-guid=3:${rootGPUID} \
$diskImage $diskImage
''} ''}
''; '';
efi = '' efi = ''
parted --script $diskImage -- \ parted --script $diskImage -- \
mklabel gpt \ mklabel gpt \
mkpart ESP fat32 8MiB ${bootSize} \ mkpart ESP fat32 8MiB ${bootSize} \
set 1 boot on \ set 1 boot on \
mkpart primary ext4 ${bootSize} -1 mkpart primary ext4 ${bootSize} -1
${lib.optionalString deterministic '' ${lib.optionalString deterministic ''
sgdisk \ sgdisk \
--disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \ --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
--partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \ --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
--partition-guid=2:${rootGPUID} \ --partition-guid=2:${rootGPUID} \
$diskImage $diskImage
''} ''}
''; '';
efixbootldr = '' efixbootldr = ''
parted --script $diskImage -- \ parted --script $diskImage -- \
mklabel gpt \ mklabel gpt \
mkpart ESP fat32 8MiB 100MiB \ mkpart ESP fat32 8MiB 100MiB \
set 1 boot on \ set 1 boot on \
mkpart BOOT fat32 100MiB ${bootSize} \ mkpart BOOT fat32 100MiB ${bootSize} \
set 2 bls_boot on \ set 2 bls_boot on \
mkpart ROOT ext4 ${bootSize} -1 mkpart ROOT ext4 ${bootSize} -1
${lib.optionalString deterministic '' ${lib.optionalString deterministic ''
sgdisk \ sgdisk \
--disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \ --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
--partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \ --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
--partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \ --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
--partition-guid=3:${rootGPUID} \ --partition-guid=3:${rootGPUID} \
$diskImage $diskImage
''} ''}
''; '';
hybrid = '' hybrid = ''
parted --script $diskImage -- \ parted --script $diskImage -- \
mklabel gpt \ mklabel gpt \
mkpart ESP fat32 8MiB ${bootSize} \ mkpart ESP fat32 8MiB ${bootSize} \
set 1 boot on \ set 1 boot on \
mkpart no-fs 0 1024KiB \ mkpart no-fs 0 1024KiB \
set 2 bios_grub on \ set 2 bios_grub on \
mkpart primary ext4 ${bootSize} -1 mkpart primary ext4 ${bootSize} -1
${lib.optionalString deterministic '' ${lib.optionalString deterministic ''
sgdisk \ sgdisk \
--disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \ --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
--partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \ --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
--partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \ --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
--partition-guid=3:${rootGPUID} \ --partition-guid=3:${rootGPUID} \
$diskImage $diskImage
''} ''}
''; '';
none = ""; none = "";
}.${partitionTableType}; }
.${partitionTableType};
useEFIBoot = touchEFIVars; useEFIBoot = touchEFIVars;
nixpkgs = lib.cleanSource pkgs.path; nixpkgs = lib.cleanSource pkgs.path;
# FIXME: merge with channel.nix / make-channel.nix. # FIXME: merge with channel.nix / make-channel.nix.
channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} '' channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" { } ''
mkdir -p $out mkdir -p $out
cp -prd ${nixpkgs.outPath} $out/nixos cp -prd ${nixpkgs.outPath} $out/nixos
chmod -R u+w $out/nixos chmod -R u+w $out/nixos
@ -314,7 +358,9 @@ let format' = format; in let
echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
''; '';
binPath = lib.makeBinPath (with pkgs; [ binPath = lib.makeBinPath (
with pkgs;
[
rsync rsync
util-linux util-linux
parted parted
@ -326,19 +372,19 @@ let format' = format; in let
systemdMinimal systemdMinimal
] ]
++ lib.optional deterministic gptfdisk ++ lib.optional deterministic gptfdisk
++ stdenv.initialPath); ++ stdenv.initialPath
);
# I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
# image building logic. The comment right below this now appears in 4 different places in nixpkgs :) # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
# !!! should use XML. # !!! should use XML.
sources = map (x: x.source) contents; sources = map (x: x.source) contents;
targets = map (x: x.target) contents; targets = map (x: x.target) contents;
modes = map (x: x.mode or "''") contents; modes = map (x: x.mode or "''") contents;
users = map (x: x.user or "''") contents; users = map (x: x.user or "''") contents;
groups = map (x: x.group or "''") contents; groups = map (x: x.group or "''") contents;
basePaths = [ config.system.build.toplevel ] basePaths = [ config.system.build.toplevel ] ++ lib.optional copyChannel channelSources;
++ lib.optional copyChannel channelSources;
additionalPaths' = lib.subtractLists basePaths additionalPaths; additionalPaths' = lib.subtractLists basePaths additionalPaths;
@ -441,75 +487,96 @@ let format' = format; in let
${if copyChannel then "--channel ${channelSources}" else "--no-channel-copy"} \ ${if copyChannel then "--channel ${channelSources}" else "--no-channel-copy"} \
--substituters "" --substituters ""
${lib.optionalString (additionalPaths' != []) '' ${lib.optionalString (additionalPaths' != [ ]) ''
nix --extra-experimental-features nix-command copy --to $root --no-check-sigs ${lib.concatStringsSep " " additionalPaths'} nix --extra-experimental-features nix-command copy --to $root --no-check-sigs ${lib.concatStringsSep " " additionalPaths'}
''} ''}
diskImage=nixos.raw diskImage=nixos.raw
${if diskSize == "auto" then '' ${
${if partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid" then '' if diskSize == "auto" then
# Add the GPT at the end ''
gptSpace=$(( 512 * 34 * 1 )) ${
# Normally we'd need to account for alignment and things, if bootSize if
# represented the actual size of the boot partition. But it instead partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid"
# represents the offset at which it ends. then
# So we know bootSize is the reserved space in front of the partition. ''
reservedSpace=$(( gptSpace + $(numfmt --from=iec '${bootSize}') )) # Add the GPT at the end
'' else if partitionTableType == "legacy+gpt" then '' gptSpace=$(( 512 * 34 * 1 ))
# Add the GPT at the end # Normally we'd need to account for alignment and things, if bootSize
gptSpace=$(( 512 * 34 * 1 )) # represented the actual size of the boot partition. But it instead
# And include the bios_grub partition; the ext4 partition starts at 2MB exactly. # represents the offset at which it ends.
reservedSpace=$(( gptSpace + 2 * mebibyte )) # So we know bootSize is the reserved space in front of the partition.
'' else if partitionTableType == "legacy" then '' reservedSpace=$(( gptSpace + $(numfmt --from=iec '${bootSize}') ))
# Add the 1MiB aligned reserved space (includes MBR) ''
reservedSpace=$(( mebibyte )) else if partitionTableType == "legacy+gpt" then
'' else '' ''
reservedSpace=0 # Add the GPT at the end
''} gptSpace=$(( 512 * 34 * 1 ))
additionalSpace=$(( $(numfmt --from=iec '${additionalSpace}') + reservedSpace )) # And include the bios_grub partition; the ext4 partition starts at 2MB exactly.
reservedSpace=$(( gptSpace + 2 * mebibyte ))
''
else if partitionTableType == "legacy" then
''
# Add the 1MiB aligned reserved space (includes MBR)
reservedSpace=$(( mebibyte ))
''
else
''
reservedSpace=0
''
}
additionalSpace=$(( $(numfmt --from=iec '${additionalSpace}') + reservedSpace ))
# Compute required space in filesystem blocks # Compute required space in filesystem blocks
diskUsage=$(find . ! -type d -print0 | du --files0-from=- --apparent-size --block-size "${blockSize}" | cut -f1 | sum_lines) diskUsage=$(find . ! -type d -print0 | du --files0-from=- --apparent-size --block-size "${blockSize}" | cut -f1 | sum_lines)
# Each inode takes space! # Each inode takes space!
numInodes=$(find . | wc -l) numInodes=$(find . | wc -l)
# Convert to bytes, inodes take two blocks each! # Convert to bytes, inodes take two blocks each!
diskUsage=$(( (diskUsage + 2 * numInodes) * ${blockSize} )) diskUsage=$(( (diskUsage + 2 * numInodes) * ${blockSize} ))
# Then increase the required space to account for the reserved blocks. # Then increase the required space to account for the reserved blocks.
fudge=$(compute_fudge $diskUsage) fudge=$(compute_fudge $diskUsage)
requiredFilesystemSpace=$(( diskUsage + fudge )) requiredFilesystemSpace=$(( diskUsage + fudge ))
diskSize=$(( requiredFilesystemSpace + additionalSpace )) diskSize=$(( requiredFilesystemSpace + additionalSpace ))
# Round up to the nearest mebibyte. # Round up to the nearest mebibyte.
# This ensures whole 512 bytes sector sizes in the disk image # This ensures whole 512 bytes sector sizes in the disk image
# and helps towards aligning partitions optimally. # and helps towards aligning partitions optimally.
if (( diskSize % mebibyte )); then if (( diskSize % mebibyte )); then
diskSize=$(( ( diskSize / mebibyte + 1) * mebibyte )) diskSize=$(( ( diskSize / mebibyte + 1) * mebibyte ))
fi fi
truncate -s "$diskSize" $diskImage truncate -s "$diskSize" $diskImage
printf "Automatic disk size...\n" printf "Automatic disk size...\n"
printf " Closure space use: %d bytes\n" $diskUsage printf " Closure space use: %d bytes\n" $diskUsage
printf " fudge: %d bytes\n" $fudge printf " fudge: %d bytes\n" $fudge
printf " Filesystem size needed: %d bytes\n" $requiredFilesystemSpace printf " Filesystem size needed: %d bytes\n" $requiredFilesystemSpace
printf " Additional space: %d bytes\n" $additionalSpace printf " Additional space: %d bytes\n" $additionalSpace
printf " Disk image size: %d bytes\n" $diskSize printf " Disk image size: %d bytes\n" $diskSize
'' else '' ''
truncate -s ${toString diskSize}M $diskImage else
''} ''
truncate -s ${toString diskSize}M $diskImage
''
}
${partitionDiskScript} ${partitionDiskScript}
${if partitionTableType != "none" then '' ${
# Get start & length of the root partition in sectors to $START and $SECTORS. if partitionTableType != "none" then
eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs) ''
# Get start & length of the root partition in sectors to $START and $SECTORS.
eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
'' else '' ''
mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage else
''} ''
mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage
''
}
echo "copying staging root to image..." echo "copying staging root to image..."
cptofs -p ${lib.optionalString (partitionTableType != "none") "-P ${rootPartition}"} \ cptofs -p ${lib.optionalString (partitionTableType != "none") "-P ${rootPartition}"} \
@ -520,11 +587,16 @@ let format' = format; in let
''; '';
moveOrConvertImage = '' moveOrConvertImage = ''
${if format == "raw" then '' ${
mv $diskImage $out/${filename} if format == "raw" then
'' else '' ''
${pkgs.qemu-utils}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename} mv $diskImage $out/${filename}
''} ''
else
''
${pkgs.qemu-utils}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
''
}
diskImage=$out/${filename} diskImage=$out/${filename}
''; '';
@ -540,118 +612,131 @@ let format' = format; in let
''; '';
buildImage = pkgs.vmTools.runInLinuxVM ( buildImage = pkgs.vmTools.runInLinuxVM (
pkgs.runCommand name { pkgs.runCommand name
preVM = prepareImage + lib.optionalString touchEFIVars createEFIVars; {
buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ]; preVM = prepareImage + lib.optionalString touchEFIVars createEFIVars;
postVM = moveOrConvertImage + createHydraBuildProducts + postVM; buildInputs = with pkgs; [
QEMU_OPTS = util-linux
lib.concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}" e2fsprogs
++ lib.optionals touchEFIVars [ dosfstools
"-drive if=pflash,format=raw,unit=1,file=$efiVars" ];
] ++ lib.optionals (OVMF.systemManagementModeRequired or false) [ postVM = moveOrConvertImage + createHydraBuildProducts + postVM;
"-machine" "q35,smm=on" QEMU_OPTS = lib.concatStringsSep " " (
"-global" "driver=cfi.pflash01,property=secure,value=on" lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
] ++ lib.optionals touchEFIVars [
); "-drive if=pflash,format=raw,unit=1,file=$efiVars"
inherit memSize; ]
} '' ++ lib.optionals (OVMF.systemManagementModeRequired or false) [
export PATH=${binPath}:$PATH "-machine"
"q35,smm=on"
"-global"
"driver=cfi.pflash01,property=secure,value=on"
]
);
inherit memSize;
}
''
export PATH=${binPath}:$PATH
rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"} rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
# It is necessary to set root filesystem unique identifier in advance, otherwise # It is necessary to set root filesystem unique identifier in advance, otherwise
# bootloader might get the wrong one and fail to boot. # bootloader might get the wrong one and fail to boot.
# At the end, we reset again because we want deterministic timestamps. # At the end, we reset again because we want deterministic timestamps.
${lib.optionalString (fsType == "ext4" && deterministic) '' ${lib.optionalString (fsType == "ext4" && deterministic) ''
tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
''} ''}
# make systemd-boot find ESP without udev # make systemd-boot find ESP without udev
mkdir /dev/block mkdir /dev/block
ln -s /dev/vda1 /dev/block/254:1 ln -s /dev/vda1 /dev/block/254:1
mountPoint=/mnt mountPoint=/mnt
mkdir $mountPoint mkdir $mountPoint
mount $rootDisk $mountPoint mount $rootDisk $mountPoint
# Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
# '-E offset=X' option, so we can't do this outside the VM. # '-E offset=X' option, so we can't do this outside the VM.
${lib.optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") '' ${lib.optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") ''
mkdir -p /mnt/boot mkdir -p /mnt/boot
mkfs.vfat -n ESP /dev/vda1 mkfs.vfat -n ESP /dev/vda1
mount /dev/vda1 /mnt/boot mount /dev/vda1 /mnt/boot
${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"} ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
''} ''}
${lib.optionalString (partitionTableType == "efixbootldr") '' ${lib.optionalString (partitionTableType == "efixbootldr") ''
mkdir -p /mnt/{boot,efi} mkdir -p /mnt/{boot,efi}
mkfs.vfat -n ESP /dev/vda1 mkfs.vfat -n ESP /dev/vda1
mkfs.vfat -n BOOT /dev/vda2 mkfs.vfat -n BOOT /dev/vda2
mount /dev/vda1 /mnt/efi mount /dev/vda1 /mnt/efi
mount /dev/vda2 /mnt/boot mount /dev/vda2 /mnt/boot
${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"} ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
''} ''}
# Install a configuration.nix # Install a configuration.nix
mkdir -p /mnt/etc/nixos mkdir -p /mnt/etc/nixos
${lib.optionalString (configFile != null) '' ${lib.optionalString (configFile != null) ''
cp ${configFile} /mnt/etc/nixos/configuration.nix cp ${configFile} /mnt/etc/nixos/configuration.nix
''} ''}
${lib.optionalString installBootLoader '' ${lib.optionalString installBootLoader ''
# In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb # In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
# Use this option to create a symlink from vda to any arbitrary device you want. # Use this option to create a symlink from vda to any arbitrary device you want.
${lib.optionalString (config.boot.loader.grub.enable) (lib.concatMapStringsSep " " (device: ${lib.optionalString (config.boot.loader.grub.enable) (
lib.optionalString (device != "/dev/vda") '' lib.concatMapStringsSep " " (
mkdir -p "$(dirname ${device})" device:
ln -s /dev/vda ${device} lib.optionalString (device != "/dev/vda") ''
'') config.boot.loader.grub.devices)} mkdir -p "$(dirname ${device})"
ln -s /dev/vda ${device}
''
) config.boot.loader.grub.devices
)}
# Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), etc. # Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), etc.
# NOTE: systemd-boot-builder.py calls nix-env --list-generations which # NOTE: systemd-boot-builder.py calls nix-env --list-generations which
# clobbers $HOME/.nix-defexpr/channels/nixos This would cause a folder # clobbers $HOME/.nix-defexpr/channels/nixos This would cause a folder
# /homeless-shelter to show up in the final image which in turn breaks # /homeless-shelter to show up in the final image which in turn breaks
# nix builds in the target image if sandboxing is turned off (through # nix builds in the target image if sandboxing is turned off (through
# __noChroot for example). # __noChroot for example).
export HOME=$TMPDIR export HOME=$TMPDIR
NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
# The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
rm -f $mountPoint/etc/machine-id rm -f $mountPoint/etc/machine-id
''} ''}
# Set the ownerships of the contents. The modes are set in preVM. # Set the ownerships of the contents. The modes are set in preVM.
# No globbing on targets, so no need to set -f # No globbing on targets, so no need to set -f
targets_=(${lib.concatStringsSep " " targets}) targets_=(${lib.concatStringsSep " " targets})
users_=(${lib.concatStringsSep " " users}) users_=(${lib.concatStringsSep " " users})
groups_=(${lib.concatStringsSep " " groups}) groups_=(${lib.concatStringsSep " " groups})
for ((i = 0; i < ''${#targets_[@]}; i++)); do for ((i = 0; i < ''${#targets_[@]}; i++)); do
target="''${targets_[$i]}" target="''${targets_[$i]}"
user="''${users_[$i]}" user="''${users_[$i]}"
group="''${groups_[$i]}" group="''${groups_[$i]}"
if [ -n "$user$group" ]; then if [ -n "$user$group" ]; then
# We have to nixos-enter since we need to use the user and group of the VM # We have to nixos-enter since we need to use the user and group of the VM
nixos-enter --root $mountPoint -- chown -R "$user:$group" "$target" nixos-enter --root $mountPoint -- chown -R "$user:$group" "$target"
fi fi
done done
umount -R /mnt umount -R /mnt
# Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
# mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
# output, of course, but we can fix that when/if we start making images deterministic. # output, of course, but we can fix that when/if we start making images deterministic.
# In deterministic mode, this is fixed to 1970-01-01 (UNIX timestamp 0). # In deterministic mode, this is fixed to 1970-01-01 (UNIX timestamp 0).
# This two-step approach is necessary otherwise `tune2fs` will want a fresher filesystem to perform # This two-step approach is necessary otherwise `tune2fs` will want a fresher filesystem to perform
# some changes. # some changes.
${lib.optionalString (fsType == "ext4") '' ${lib.optionalString (fsType == "ext4") ''
tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
${lib.optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"} ${lib.optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"}
''} ''}
'' ''
); );
in in
if onlyNixStore then if onlyNixStore then
pkgs.runCommand name {} pkgs.runCommand name { } (prepareImage + moveOrConvertImage + createHydraBuildProducts + postVM)
(prepareImage + moveOrConvertImage + createHydraBuildProducts + postVM) else
else buildImage buildImage

View file

@ -1,18 +1,22 @@
{ lib, stdenv, squashfsTools, closureInfo {
lib,
stdenv,
squashfsTools,
closureInfo,
, fileName ? "squashfs" fileName ? "squashfs",
, # The root directory of the squashfs filesystem is filled with the # The root directory of the squashfs filesystem is filled with the
# closures of the Nix store paths listed here. # closures of the Nix store paths listed here.
storeContents ? [] storeContents ? [ ],
# Pseudo files to be added to squashfs image # Pseudo files to be added to squashfs image
, pseudoFiles ? [] pseudoFiles ? [ ],
, noStrip ? false noStrip ? false,
, # Compression parameters. # Compression parameters.
# For zstd compression you can use "zstd -Xcompression-level 6". # For zstd compression you can use "zstd -Xcompression-level 6".
comp ? "xz -Xdict-size 100%" comp ? "xz -Xdict-size 100%",
, # create hydra build product. will put image in directory instead # create hydra build product. will put image in directory instead
# of directly in the store # of directly in the store
hydraBuildProduct ? false hydraBuildProduct ? false,
}: }:
let let
@ -34,24 +38,28 @@ stdenv.mkDerivation {
cp $closureInfo/registration nix-path-registration cp $closureInfo/registration nix-path-registration
imgPath="$out" imgPath="$out"
'' + lib.optionalString hydraBuildProduct '' ''
+ lib.optionalString hydraBuildProduct ''
mkdir $out mkdir $out
imgPath="$out/${fileName}.squashfs" imgPath="$out/${fileName}.squashfs"
'' + lib.optionalString stdenv.buildPlatform.is32bit '' ''
+ lib.optionalString stdenv.buildPlatform.is32bit ''
# 64 cores on i686 does not work # 64 cores on i686 does not work
# fails with FATAL ERROR: mangle2:: xz compress failed with error code 5 # fails with FATAL ERROR: mangle2:: xz compress failed with error code 5
if ((NIX_BUILD_CORES > 48)); then if ((NIX_BUILD_CORES > 48)); then
NIX_BUILD_CORES=48 NIX_BUILD_CORES=48
fi fi
'' + '' ''
+ ''
# Generate the squashfs image. # Generate the squashfs image.
mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $imgPath ${pseudoFilesArgs} \ mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $imgPath ${pseudoFilesArgs} \
-no-hardlinks ${lib.optionalString noStrip "-no-strip"} -keep-as-directory -all-root -b 1048576 ${compFlag} \ -no-hardlinks ${lib.optionalString noStrip "-no-strip"} -keep-as-directory -all-root -b 1048576 ${compFlag} \
-processors $NIX_BUILD_CORES -root-mode 0755 -processors $NIX_BUILD_CORES -root-mode 0755
'' + lib.optionalString hydraBuildProduct '' ''
+ lib.optionalString hydraBuildProduct ''
mkdir -p $out/nix-support mkdir -p $out/nix-support
echo "file squashfs-image $out/${fileName}.squashfs" >> $out/nix-support/hydra-build-products echo "file squashfs-image $out/${fileName}.squashfs" >> $out/nix-support/hydra-build-products

File diff suppressed because it is too large Load diff

View file

@ -30,24 +30,39 @@ let
checkService = checkUnitConfig "Service" [ checkService = checkUnitConfig "Service" [
(assertValueOneOf "Type" [ (assertValueOneOf "Type" [
"exec" "simple" "forking" "oneshot" "dbus" "notify" "notify-reload" "idle" "exec"
"simple"
"forking"
"oneshot"
"dbus"
"notify"
"notify-reload"
"idle"
]) ])
(assertValueOneOf "Restart" [ (assertValueOneOf "Restart" [
"no" "on-success" "on-failure" "on-abnormal" "on-abort" "always" "no"
"on-success"
"on-failure"
"on-abnormal"
"on-abort"
"always"
]) ])
]; ];
in rec { in
rec {
unitOption = mkOptionType { unitOption = mkOptionType {
name = "systemd option"; name = "systemd option";
merge = loc: defs: merge =
loc: defs:
let let
defs' = filterOverrides defs; defs' = filterOverrides defs;
in in
if any (def: isList def.value) defs' if any (def: isList def.value) defs' then
then concatMap (def: toList def.value) defs' concatMap (def: toList def.value) defs'
else mergeEqualOption loc defs'; else
mergeEqualOption loc defs';
}; };
sharedOptions = { sharedOptions = {
@ -76,7 +91,10 @@ in rec {
overrideStrategy = mkOption { overrideStrategy = mkOption {
default = "asDropinIfExists"; default = "asDropinIfExists";
type = types.enum [ "asDropinIfExists" "asDropin" ]; type = types.enum [
"asDropinIfExists"
"asDropin"
];
description = '' description = ''
Defines how unit configuration is provided for systemd: Defines how unit configuration is provided for systemd:
@ -91,7 +109,7 @@ in rec {
}; };
requiredBy = mkOption { requiredBy = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Units that require (i.e. depend on and need to go down with) this unit. Units that require (i.e. depend on and need to go down with) this unit.
@ -101,7 +119,7 @@ in rec {
}; };
upheldBy = mkOption { upheldBy = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Keep this unit running as long as the listed units are running. This is a continuously Keep this unit running as long as the listed units are running. This is a continuously
@ -110,7 +128,7 @@ in rec {
}; };
wantedBy = mkOption { wantedBy = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Units that want (i.e. depend on) this unit. The default method for Units that want (i.e. depend on) this unit. The default method for
@ -128,7 +146,7 @@ in rec {
}; };
aliases = mkOption { aliases = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = "Aliases of that unit."; description = "Aliases of that unit.";
}; };
@ -160,13 +178,13 @@ in rec {
}; };
documentation = mkOption { documentation = mkOption {
default = []; default = [ ];
type = types.listOf types.str; type = types.listOf types.str;
description = "A list of URIs referencing documentation for this unit or its configuration."; description = "A list of URIs referencing documentation for this unit or its configuration.";
}; };
requires = mkOption { requires = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Start the specified units when this unit is started, and stop Start the specified units when this unit is started, and stop
@ -175,7 +193,7 @@ in rec {
}; };
wants = mkOption { wants = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Start the specified units when this unit is started. Start the specified units when this unit is started.
@ -183,7 +201,7 @@ in rec {
}; };
upholds = mkOption { upholds = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Keeps the specified running while this unit is running. A continuous version of `wants`. Keeps the specified running while this unit is running. A continuous version of `wants`.
@ -191,7 +209,7 @@ in rec {
}; };
after = mkOption { after = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
If the specified units are started at the same time as If the specified units are started at the same time as
@ -200,7 +218,7 @@ in rec {
}; };
before = mkOption { before = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
If the specified units are started at the same time as If the specified units are started at the same time as
@ -209,7 +227,7 @@ in rec {
}; };
bindsTo = mkOption { bindsTo = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Like requires, but in addition, if the specified units Like requires, but in addition, if the specified units
@ -218,7 +236,7 @@ in rec {
}; };
partOf = mkOption { partOf = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
If the specified units are stopped or restarted, then this If the specified units are stopped or restarted, then this
@ -227,7 +245,7 @@ in rec {
}; };
conflicts = mkOption { conflicts = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
If the specified units are started, then this unit is stopped If the specified units are started, then this unit is stopped
@ -236,7 +254,7 @@ in rec {
}; };
requisite = mkOption { requisite = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
Similar to requires. However if the units listed are not started, Similar to requires. However if the units listed are not started,
@ -245,8 +263,10 @@ in rec {
}; };
unitConfig = mkOption { unitConfig = mkOption {
default = {}; default = { };
example = { RequiresMountsFor = "/data"; }; example = {
RequiresMountsFor = "/data";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -256,7 +276,7 @@ in rec {
}; };
onFailure = mkOption { onFailure = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
A list of one or more units that are activated when A list of one or more units that are activated when
@ -265,7 +285,7 @@ in rec {
}; };
onSuccess = mkOption { onSuccess = mkOption {
default = []; default = [ ];
type = types.listOf unitNameType; type = types.listOf unitNameType;
description = '' description = ''
A list of one or more units that are activated when A list of one or more units that are activated when
@ -274,21 +294,21 @@ in rec {
}; };
startLimitBurst = mkOption { startLimitBurst = mkOption {
type = types.int; type = types.int;
description = '' description = ''
Configure unit start rate limiting. Units which are started Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time more than startLimitBurst times within an interval time
interval are not permitted to start any more. interval are not permitted to start any more.
''; '';
}; };
startLimitIntervalSec = mkOption { startLimitIntervalSec = mkOption {
type = types.int; type = types.int;
description = '' description = ''
Configure unit start rate limiting. Units which are started Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time more than startLimitBurst times within an interval time
interval are not permitted to start any more. interval are not permitted to start any more.
''; '';
}; };
}; };
@ -301,7 +321,7 @@ in rec {
options = { options = {
restartTriggers = mkOption { restartTriggers = mkOption {
default = []; default = [ ];
type = types.listOf types.unspecified; type = types.listOf types.unspecified;
description = '' description = ''
An arbitrary list of items such as derivations. If any item An arbitrary list of items such as derivations. If any item
@ -311,7 +331,7 @@ in rec {
}; };
reloadTriggers = mkOption { reloadTriggers = mkOption {
default = []; default = [ ];
type = types.listOf unitOption; type = types.listOf unitOption;
description = '' description = ''
An arbitrary list of items such as derivations. If any item An arbitrary list of items such as derivations. If any item
@ -324,170 +344,188 @@ in rec {
}; };
stage1CommonUnitOptions = commonUnitOptions; stage1CommonUnitOptions = commonUnitOptions;
serviceOptions = { name, config, ... }: { serviceOptions =
options = { { name, config, ... }:
{
options = {
environment = mkOption { environment = mkOption {
default = {}; default = { };
type = with types; attrsOf (nullOr (oneOf [ str path package ])); type =
example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; }; with types;
description = "Environment variables passed to the service's processes."; attrsOf (
}; nullOr (oneOf [
str
path = mkOption { path
default = []; package
type = with types; listOf (oneOf [ package str ]); ])
description = '' );
Packages added to the service's {env}`PATH` example = {
environment variable. Both the {file}`bin` PATH = "/foo/bar/bin";
and {file}`sbin` subdirectories of each LANG = "nl_NL.UTF-8";
package are added.
'';
};
serviceConfig = mkOption {
default = {};
example =
{ RestartSec = 5;
}; };
type = types.addCheck (types.attrsOf unitOption) checkService; description = "Environment variables passed to the service's processes.";
description = '' };
Each attribute in this set specifies an option in the
`[Service]` section of the unit. See path = mkOption {
{manpage}`systemd.service(5)` for details. default = [ ];
''; type =
with types;
listOf (oneOf [
package
str
]);
description = ''
Packages added to the service's {env}`PATH`
environment variable. Both the {file}`bin`
and {file}`sbin` subdirectories of each
package are added.
'';
};
serviceConfig = mkOption {
default = { };
example = {
RestartSec = 5;
};
type = types.addCheck (types.attrsOf unitOption) checkService;
description = ''
Each attribute in this set specifies an option in the
`[Service]` section of the unit. See
{manpage}`systemd.service(5)` for details.
'';
};
enableStrictShellChecks = mkOption {
type = types.bool;
description = "Enable running shellcheck on the generated scripts for this unit.";
# The default gets set in systemd-lib.nix because we don't have access to
# the full NixOS config here.
defaultText = literalExpression "config.systemd.enableStrictShellChecks";
};
script = mkOption {
type = types.lines;
default = "";
description = "Shell commands executed as the service's main process.";
};
scriptArgs = mkOption {
type = types.str;
default = "";
example = "%i";
description = ''
Arguments passed to the main process script.
Can contain specifiers (`%` placeholders expanded by systemd, see {manpage}`systemd.unit(5)`).
'';
};
preStart = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed before the service's main process
is started.
'';
};
postStart = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed after the service's main process
is started.
'';
};
reload = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed when the service's main process
is reloaded.
'';
};
preStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed to stop the service.
'';
};
postStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed after the service's main process
has exited.
'';
};
jobScripts = mkOption {
type = with types; coercedTo path singleton (listOf path);
internal = true;
description = "A list of all job script derivations of this unit.";
default = [ ];
};
}; };
enableStrictShellChecks = mkOption { config = mkMerge [
type = types.bool; (mkIf (config.preStart != "") rec {
description = "Enable running shellcheck on the generated scripts for this unit."; jobScripts = makeJobScript {
# The default gets set in systemd-lib.nix because we don't have access to name = "${name}-pre-start";
# the full NixOS config here. text = config.preStart;
defaultText = literalExpression "config.systemd.enableStrictShellChecks"; inherit (config) enableStrictShellChecks;
}; };
serviceConfig.ExecStartPre = [ jobScripts ];
script = mkOption { })
type = types.lines; (mkIf (config.script != "") rec {
default = ""; jobScripts = makeJobScript {
description = "Shell commands executed as the service's main process."; name = "${name}-start";
}; text = config.script;
inherit (config) enableStrictShellChecks;
scriptArgs = mkOption { };
type = types.str; serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
default = ""; })
example = "%i"; (mkIf (config.postStart != "") rec {
description = '' jobScripts = makeJobScript {
Arguments passed to the main process script. name = "${name}-post-start";
Can contain specifiers (`%` placeholders expanded by systemd, see {manpage}`systemd.unit(5)`). text = config.postStart;
''; inherit (config) enableStrictShellChecks;
}; };
serviceConfig.ExecStartPost = [ jobScripts ];
preStart = mkOption { })
type = types.lines; (mkIf (config.reload != "") rec {
default = ""; jobScripts = makeJobScript {
description = '' name = "${name}-reload";
Shell commands executed before the service's main process text = config.reload;
is started. inherit (config) enableStrictShellChecks;
''; };
}; serviceConfig.ExecReload = jobScripts;
})
postStart = mkOption { (mkIf (config.preStop != "") rec {
type = types.lines; jobScripts = makeJobScript {
default = ""; name = "${name}-pre-stop";
description = '' text = config.preStop;
Shell commands executed after the service's main process inherit (config) enableStrictShellChecks;
is started. };
''; serviceConfig.ExecStop = jobScripts;
}; })
(mkIf (config.postStop != "") rec {
reload = mkOption { jobScripts = makeJobScript {
type = types.lines; name = "${name}-post-stop";
default = ""; text = config.postStop;
description = '' inherit (config) enableStrictShellChecks;
Shell commands executed when the service's main process };
is reloaded. serviceConfig.ExecStopPost = jobScripts;
''; })
}; ];
preStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed to stop the service.
'';
};
postStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed after the service's main process
has exited.
'';
};
jobScripts = mkOption {
type = with types; coercedTo path singleton (listOf path);
internal = true;
description = "A list of all job script derivations of this unit.";
default = [];
};
}; };
config = mkMerge [
(mkIf (config.preStart != "") rec {
jobScripts = makeJobScript {
name = "${name}-pre-start";
text = config.preStart;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStartPre = [ jobScripts ];
})
(mkIf (config.script != "") rec {
jobScripts = makeJobScript {
name = "${name}-start";
text = config.script;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
})
(mkIf (config.postStart != "") rec {
jobScripts = makeJobScript {
name = "${name}-post-start";
text = config.postStart;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStartPost = [ jobScripts ];
})
(mkIf (config.reload != "") rec {
jobScripts = makeJobScript {
name = "${name}-reload";
text = config.reload;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecReload = jobScripts;
})
(mkIf (config.preStop != "") rec {
jobScripts = makeJobScript {
name = "${name}-pre-stop";
text = config.preStop;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStop = jobScripts;
})
(mkIf (config.postStop != "") rec {
jobScripts = makeJobScript {
name = "${name}-post-stop";
text = config.postStop;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStopPost = jobScripts;
})
];
};
stage2ServiceOptions = { stage2ServiceOptions = {
imports = [ imports = [
stage2CommonUnitOptions stage2CommonUnitOptions
@ -537,7 +575,7 @@ in rec {
startAt = mkOption { startAt = mkOption {
type = with types; either str (listOf str); type = with types; either str (listOf str);
default = []; default = [ ];
example = "Sun 14:00:00"; example = "Sun 14:00:00";
description = '' description = ''
Automatically start this unit at the given date/time, which Automatically start this unit at the given date/time, which
@ -558,14 +596,16 @@ in rec {
]; ];
}; };
socketOptions = { socketOptions = {
options = { options = {
listenStreams = mkOption { listenStreams = mkOption {
default = []; default = [ ];
type = types.listOf types.str; type = types.listOf types.str;
example = [ "0.0.0.0:993" "/run/my-socket" ]; example = [
"0.0.0.0:993"
"/run/my-socket"
];
description = '' description = ''
For each item in this list, a `ListenStream` For each item in this list, a `ListenStream`
option in the `[Socket]` section will be created. option in the `[Socket]` section will be created.
@ -573,9 +613,12 @@ in rec {
}; };
listenDatagrams = mkOption { listenDatagrams = mkOption {
default = []; default = [ ];
type = types.listOf types.str; type = types.listOf types.str;
example = [ "0.0.0.0:993" "/run/my-socket" ]; example = [
"0.0.0.0:993"
"/run/my-socket"
];
description = '' description = ''
For each item in this list, a `ListenDatagram` For each item in this list, a `ListenDatagram`
option in the `[Socket]` section will be created. option in the `[Socket]` section will be created.
@ -583,8 +626,10 @@ in rec {
}; };
socketConfig = mkOption { socketConfig = mkOption {
default = {}; default = { };
example = { ListenStream = "/run/my-socket"; }; example = {
ListenStream = "/run/my-socket";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -610,13 +655,15 @@ in rec {
]; ];
}; };
timerOptions = { timerOptions = {
options = { options = {
timerConfig = mkOption { timerConfig = mkOption {
default = {}; default = { };
example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; }; example = {
OnCalendar = "Sun 14:00:00";
Unit = "foo.service";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -643,13 +690,15 @@ in rec {
]; ];
}; };
pathOptions = { pathOptions = {
options = { options = {
pathConfig = mkOption { pathConfig = mkOption {
default = {}; default = { };
example = { PathChanged = "/some/path"; Unit = "changedpath.service"; }; example = {
PathChanged = "/some/path";
Unit = "changedpath.service";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -675,7 +724,6 @@ in rec {
]; ];
}; };
mountOptions = { mountOptions = {
options = { options = {
@ -709,8 +757,10 @@ in rec {
}; };
mountConfig = mkOption { mountConfig = mkOption {
default = {}; default = { };
example = { DirectoryMode = "0775"; }; example = {
DirectoryMode = "0775";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -749,8 +799,10 @@ in rec {
}; };
automountConfig = mkOption { automountConfig = mkOption {
default = {}; default = { };
example = { DirectoryMode = "0775"; }; example = {
DirectoryMode = "0775";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the
@ -780,8 +832,10 @@ in rec {
options = { options = {
sliceConfig = mkOption { sliceConfig = mkOption {
default = {}; default = { };
example = { MemoryMax = "2G"; }; example = {
MemoryMax = "2G";
};
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
description = '' description = ''
Each attribute in this set specifies an option in the Each attribute in this set specifies an option in the

View file

@ -1,4 +1,9 @@
{ config, lib, hostPkgs, ... }: {
config,
lib,
hostPkgs,
...
}:
let let
inherit (lib) mkOption types literalMD; inherit (lib) mkOption types literalMD;
@ -11,10 +16,9 @@ let
tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; }; tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
}; };
vlans = map (
vlans = map (m: ( m: (m.virtualisation.vlans ++ (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))
m.virtualisation.vlans ++ ) (lib.attrValues config.nodes);
(lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))) (lib.attrValues config.nodes);
vms = map (m: m.system.build.vm) (lib.attrValues config.nodes); vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
nodeHostNames = nodeHostNames =
@ -23,13 +27,14 @@ let
in in
nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine"; nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
pythonizeName = name: pythonizeName =
name:
let let
head = lib.substring 0 1 name; head = lib.substring 0 1 name;
tail = lib.substring 1 (-1) name; tail = lib.substring 1 (-1) name;
in in
(if builtins.match "[A-z_]" head == null then "_" else head) + (if builtins.match "[A-z_]" head == null then "_" else head)
lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail; + lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
uniqueVlans = lib.unique (builtins.concatLists vlans); uniqueVlans = lib.unique (builtins.concatLists vlans);
vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans; vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
@ -96,7 +101,12 @@ let
--set testScript "$out/test-script" \ --set testScript "$out/test-script" \
--set globalTimeout "${toString config.globalTimeout}" \ --set globalTimeout "${toString config.globalTimeout}" \
--set vlans '${toString vlans}' \ --set vlans '${toString vlans}' \
${lib.escapeShellArgs (lib.concatMap (arg: ["--add-flags" arg]) config.extraDriverArgs)} ${lib.escapeShellArgs (
lib.concatMap (arg: [
"--add-flags"
arg
]) config.extraDriverArgs
)}
''; '';
in in
@ -165,7 +175,7 @@ in
They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`. They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
''; '';
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
}; };
skipLint = mkOption { skipLint = mkOption {
@ -191,8 +201,7 @@ in
_module.args = { _module.args = {
hostPkgs = hostPkgs =
# Comment is in nixos/modules/misc/nixpkgs.nix # Comment is in nixos/modules/misc/nixpkgs.nix
lib.mkOverride lib.modules.defaultOverridePriority lib.mkOverride lib.modules.defaultOverridePriority config.hostPkgs.__splicedPackages;
config.hostPkgs.__splicedPackages;
}; };
driver = withChecks driver; driver = withChecks driver;

View file

@ -1,6 +1,11 @@
# nix-build '<nixpkgs/nixos>' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }" # nix-build '<nixpkgs/nixos>' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }"
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
copyChannel = true; copyChannel = true;
in in
@ -16,12 +21,11 @@ in
additionalSpace = "1024M"; additionalSpace = "1024M";
pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
format = "qcow2"; format = "qcow2";
configFile = pkgs.writeText "configuration.nix" configFile = pkgs.writeText "configuration.nix" ''
'' {
{ imports = [ <nixpkgs/nixos/modules/virtualisation/openstack-config.nix> ];
imports = [ <nixpkgs/nixos/modules/virtualisation/openstack-config.nix> ]; }
} '';
'';
}; };
} }

View file

@ -1,18 +1,26 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.console; cfg = config.console;
makeColor = i: lib.concatMapStringsSep "," (x: "0x" + lib.substring (2*i) 2 x); makeColor = i: lib.concatMapStringsSep "," (x: "0x" + lib.substring (2 * i) 2 x);
isUnicode = lib.hasSuffix "UTF-8" (lib.toUpper config.i18n.defaultLocale); isUnicode = lib.hasSuffix "UTF-8" (lib.toUpper config.i18n.defaultLocale);
optimizedKeymap = pkgs.runCommand "keymap" { optimizedKeymap =
nativeBuildInputs = [ pkgs.buildPackages.kbd ]; pkgs.runCommand "keymap"
LOADKEYS_KEYMAP_PATH = "${consoleEnv pkgs.kbd}/share/keymaps/**"; {
preferLocalBuild = true; nativeBuildInputs = [ pkgs.buildPackages.kbd ];
} '' LOADKEYS_KEYMAP_PATH = "${consoleEnv pkgs.kbd}/share/keymaps/**";
loadkeys -b ${lib.optionalString isUnicode "-u"} "${cfg.keyMap}" > $out preferLocalBuild = true;
''; }
''
loadkeys -b ${lib.optionalString isUnicode "-u"} "${cfg.keyMap}" > $out
'';
# Sadly, systemd-vconsole-setup doesn't support binary keymaps. # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
vconsoleConf = pkgs.writeText "vconsole.conf" '' vconsoleConf = pkgs.writeText "vconsole.conf" ''
@ -20,22 +28,24 @@ let
${lib.optionalString (cfg.font != null) "FONT=${cfg.font}"} ${lib.optionalString (cfg.font != null) "FONT=${cfg.font}"}
''; '';
consoleEnv = kbd: pkgs.buildEnv { consoleEnv =
name = "console-env"; kbd:
paths = [ kbd ] ++ cfg.packages; pkgs.buildEnv {
pathsToLink = [ name = "console-env";
"/share/consolefonts" paths = [ kbd ] ++ cfg.packages;
"/share/consoletrans" pathsToLink = [
"/share/keymaps" "/share/consolefonts"
"/share/unimaps" "/share/consoletrans"
]; "/share/keymaps"
}; "/share/unimaps"
];
};
in in
{ {
###### interface ###### interface
options.console = { options.console = {
enable = lib.mkEnableOption "virtual console" // { enable = lib.mkEnableOption "virtual console" // {
default = true; default = true;
}; };
@ -70,10 +80,22 @@ in
type = with lib.types; listOf (strMatching "[[:xdigit:]]{6}"); type = with lib.types; listOf (strMatching "[[:xdigit:]]{6}");
default = [ ]; default = [ ];
example = [ example = [
"002b36" "dc322f" "859900" "b58900" "002b36"
"268bd2" "d33682" "2aa198" "eee8d5" "dc322f"
"002b36" "cb4b16" "586e75" "657b83" "859900"
"839496" "6c71c4" "93a1a1" "fdf6e3" "b58900"
"268bd2"
"d33682"
"2aa198"
"eee8d5"
"002b36"
"cb4b16"
"586e75"
"657b83"
"839496"
"6c71c4"
"93a1a1"
"fdf6e3"
]; ];
description = '' description = ''
The 16 colors palette used by the virtual consoles. The 16 colors palette used by the virtual consoles.
@ -112,20 +134,24 @@ in
}; };
###### implementation ###### implementation
config = lib.mkMerge [ config = lib.mkMerge [
{ console.keyMap = with config.services.xserver; {
lib.mkIf cfg.useXkbConfig console.keyMap =
(pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } '' with config.services.xserver;
lib.mkIf cfg.useXkbConfig (
pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
'${pkgs.buildPackages.ckbcomp}/bin/ckbcomp' \ '${pkgs.buildPackages.ckbcomp}/bin/ckbcomp' \
${lib.optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT) ${
"-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}" lib.optionalString (
config.environment.sessionVariables ? XKB_CONFIG_ROOT
) "-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
} \ } \
-model '${xkb.model}' -layout '${xkb.layout}' \ -model '${xkb.model}' -layout '${xkb.layout}' \
-option '${xkb.options}' -variant '${xkb.variant}' > "$out" -option '${xkb.options}' -variant '${xkb.variant}' > "$out"
''); ''
);
} }
(lib.mkIf (!cfg.enable) { (lib.mkIf (!cfg.enable) {
@ -138,83 +164,103 @@ in
}; };
}) })
(lib.mkIf cfg.enable (lib.mkMerge [ (lib.mkIf cfg.enable (
{ environment.systemPackages = [ pkgs.kbd ]; lib.mkMerge [
{
environment.systemPackages = [ pkgs.kbd ];
# Let systemd-vconsole-setup.service do the work of setting up the # Let systemd-vconsole-setup.service do the work of setting up the
# virtual consoles. # virtual consoles.
environment.etc."vconsole.conf".source = vconsoleConf; environment.etc."vconsole.conf".source = vconsoleConf;
# Provide kbd with additional packages. # Provide kbd with additional packages.
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share"; environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
boot.initrd.preLVMCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (lib.mkBefore '' boot.initrd.preLVMCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console lib.mkBefore ''
printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
loadkmap < ${optimizedKeymap} printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
loadkmap < ${optimizedKeymap}
${lib.optionalString (cfg.earlySetup && cfg.font != null) '' ${lib.optionalString (cfg.earlySetup && cfg.font != null) ''
setfont -C /dev/console $extraUtils/share/consolefonts/font.psf setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
''} ''}
''); ''
);
boot.initrd.systemd.contents = { boot.initrd.systemd.contents = {
"/etc/vconsole.conf".source = vconsoleConf; "/etc/vconsole.conf".source = vconsoleConf;
# Add everything if we want full console setup... # Add everything if we want full console setup...
"/etc/kbd" = lib.mkIf cfg.earlySetup { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share"; }; "/etc/kbd" = lib.mkIf cfg.earlySetup {
# ...but only the keymaps if we don't source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share";
"/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps"; }; };
}; # ...but only the keymaps if we don't
boot.initrd.systemd.additionalUpstreamUnits = [ "/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) {
"systemd-vconsole-setup.service" source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps";
]; };
boot.initrd.systemd.storePaths = [
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
"${config.boot.initrd.systemd.package.kbd}/bin/setfont"
"${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
"${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
] ++ lib.optionals (cfg.font != null && lib.hasPrefix builtins.storeDir cfg.font) [
"${cfg.font}"
] ++ lib.optionals (lib.hasPrefix builtins.storeDir cfg.keyMap) [
"${cfg.keyMap}"
];
systemd.services.reload-systemd-vconsole-setup =
{ description = "Reset console on configuration changes";
wantedBy = [ "multi-user.target" ];
restartTriggers = [ vconsoleConf (consoleEnv pkgs.kbd) ];
reloadIfChanged = true;
serviceConfig =
{ RemainAfterExit = true;
ExecStart = "${pkgs.coreutils}/bin/true";
ExecReload = "/run/current-system/systemd/bin/systemctl restart systemd-vconsole-setup";
};
}; };
} boot.initrd.systemd.additionalUpstreamUnits = [
"systemd-vconsole-setup.service"
];
boot.initrd.systemd.storePaths =
[
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
"${config.boot.initrd.systemd.package.kbd}/bin/setfont"
"${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
"${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
]
++ lib.optionals (cfg.font != null && lib.hasPrefix builtins.storeDir cfg.font) [
"${cfg.font}"
]
++ lib.optionals (lib.hasPrefix builtins.storeDir cfg.keyMap) [
"${cfg.keyMap}"
];
(lib.mkIf (cfg.colors != []) { systemd.services.reload-systemd-vconsole-setup = {
boot.kernelParams = [ description = "Reset console on configuration changes";
"vt.default_red=${makeColor 0 cfg.colors}" wantedBy = [ "multi-user.target" ];
"vt.default_grn=${makeColor 1 cfg.colors}" restartTriggers = [
"vt.default_blu=${makeColor 2 cfg.colors}" vconsoleConf
]; (consoleEnv pkgs.kbd)
}) ];
reloadIfChanged = true;
serviceConfig = {
RemainAfterExit = true;
ExecStart = "${pkgs.coreutils}/bin/true";
ExecReload = "/run/current-system/systemd/bin/systemctl restart systemd-vconsole-setup";
};
};
}
(lib.mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) { (lib.mkIf (cfg.colors != [ ]) {
boot.initrd.extraUtilsCommands = '' boot.kernelParams = [
mkdir -p $out/share/consolefonts "vt.default_red=${makeColor 0 cfg.colors}"
${if lib.substring 0 1 cfg.font == "/" then '' "vt.default_grn=${makeColor 1 cfg.colors}"
font="${cfg.font}" "vt.default_blu=${makeColor 2 cfg.colors}"
'' else '' ];
font="$(echo ${consoleEnv pkgs.kbd}/share/consolefonts/${cfg.font}.*)" })
''}
if [[ $font == *.gz ]]; then (lib.mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) {
gzip -cd $font > $out/share/consolefonts/font.psf boot.initrd.extraUtilsCommands = ''
else mkdir -p $out/share/consolefonts
cp -L $font $out/share/consolefonts/font.psf ${
fi if lib.substring 0 1 cfg.font == "/" then
''; ''
}) font="${cfg.font}"
])) ''
else
''
font="$(echo ${consoleEnv pkgs.kbd}/share/consolefonts/${cfg.font}.*)"
''
}
if [[ $font == *.gz ]]; then
gzip -cd $font > $out/share/consolefonts/font.psf
else
cp -L $font $out/share/consolefonts/font.psf
fi
'';
})
]
))
]; ];
imports = [ imports = [

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
###### interface ###### interface
@ -39,8 +44,11 @@
extraLocaleSettings = lib.mkOption { extraLocaleSettings = lib.mkOption {
type = lib.types.attrsOf lib.types.str; type = lib.types.attrsOf lib.types.str;
default = {}; default = { };
example = { LC_MESSAGES = "en_US.UTF-8"; LC_TIME = "de_DE.UTF-8"; }; example = {
LC_MESSAGES = "en_US.UTF-8";
LC_TIME = "de_DE.UTF-8";
};
description = '' description = ''
A set of additional system-wide locale settings other than A set of additional system-wide locale settings other than
`LANG` which can be configured with `LANG` which can be configured with
@ -50,14 +58,18 @@
supportedLocales = lib.mkOption { supportedLocales = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = lib.unique default = lib.unique (
(builtins.map (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") ( builtins.map
[ (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8")
"C.UTF-8" (
"en_US.UTF-8" [
config.i18n.defaultLocale "C.UTF-8"
] ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings)) "en_US.UTF-8"
)); config.i18n.defaultLocale
]
++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
)
);
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
lib.unique lib.unique
(builtins.map (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") ( (builtins.map (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") (
@ -68,7 +80,11 @@
] ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings)) ] ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
)) ))
''; '';
example = ["en_US.UTF-8/UTF-8" "nl_NL.UTF-8/UTF-8" "nl_NL/ISO-8859-1"]; example = [
"en_US.UTF-8/UTF-8"
"nl_NL.UTF-8/UTF-8"
"nl_NL/ISO-8859-1"
];
description = '' description = ''
List of locales that the system should support. The value List of locales that the system should support. The value
`"all"` means that all locales supported by `"all"` means that all locales supported by
@ -81,30 +97,30 @@
}; };
###### implementation ###### implementation
config = { config = {
environment.systemPackages = environment.systemPackages =
# We increase the priority a little, so that plain glibc in systemPackages can't win. # We increase the priority a little, so that plain glibc in systemPackages can't win.
lib.optional (config.i18n.supportedLocales != []) (lib.setPrio (-1) config.i18n.glibcLocales); lib.optional (config.i18n.supportedLocales != [ ]) (lib.setPrio (-1) config.i18n.glibcLocales);
environment.sessionVariables = environment.sessionVariables = {
{ LANG = config.i18n.defaultLocale; LANG = config.i18n.defaultLocale;
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
} // config.i18n.extraLocaleSettings; } // config.i18n.extraLocaleSettings;
systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != []) { systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != [ ]) {
LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive"; LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
}; };
# /etc/locale.conf is used by systemd. # /etc/locale.conf is used by systemd.
environment.etc."locale.conf".source = pkgs.writeText "locale.conf" environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
'' LANG=${config.i18n.defaultLocale}
LANG=${config.i18n.defaultLocale} ${lib.concatStringsSep "\n" (
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings)} lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
''; )}
'';
}; };
} }

View file

@ -1,11 +1,24 @@
# /etc files related to networking, such as /etc/services. # /etc files related to networking, such as /etc/services.
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
let let
cfg = config.networking; cfg = config.networking;
opt = options.networking; opt = options.networking;
localhostMultiple = lib.any (lib.elem "localhost") (lib.attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ])); localhostMultiple = lib.any (lib.elem "localhost") (
lib.attrValues (
removeAttrs cfg.hosts [
"127.0.0.1"
"::1"
]
)
);
in in
@ -136,7 +149,7 @@ in
envVars = lib.mkOption { envVars = lib.mkOption {
type = lib.types.attrs; type = lib.types.attrs;
internal = true; internal = true;
default = {}; default = { };
description = '' description = ''
Environment variables used for the network proxy. Environment variables used for the network proxy.
''; '';
@ -146,50 +159,63 @@ in
config = { config = {
assertions = [{ assertions = [
assertion = !localhostMultiple; {
message = '' assertion = !localhostMultiple;
`networking.hosts` maps "localhost" to something other than "127.0.0.1" message = ''
or "::1". This will break some applications. Please use `networking.hosts` maps "localhost" to something other than "127.0.0.1"
`networking.extraHosts` if you really want to add such a mapping. or "::1". This will break some applications. Please use
''; `networking.extraHosts` if you really want to add such a mapping.
}]; '';
}
];
# These entries are required for "hostname -f" and to resolve both the # These entries are required for "hostname -f" and to resolve both the
# hostname and FQDN correctly: # hostname and FQDN correctly:
networking.hosts = let networking.hosts =
hostnames = # Note: The FQDN (canonical hostname) has to come first: let
lib.optional (cfg.hostName != "" && cfg.domain != null) "${cfg.hostName}.${cfg.domain}" hostnames = # Note: The FQDN (canonical hostname) has to come first:
++ lib.optional (cfg.hostName != "") cfg.hostName; # Then the hostname (without the domain) lib.optional (cfg.hostName != "" && cfg.domain != null) "${cfg.hostName}.${cfg.domain}"
in { ++ lib.optional (cfg.hostName != "") cfg.hostName; # Then the hostname (without the domain)
"127.0.0.2" = hostnames; in
} // lib.optionalAttrs cfg.enableIPv6 { {
"::1" = hostnames; "127.0.0.2" = hostnames;
}; }
// lib.optionalAttrs cfg.enableIPv6 {
"::1" = hostnames;
};
networking.hostFiles = let networking.hostFiles =
# Note: localhostHosts has to appear first in /etc/hosts so that 127.0.0.1 let
# resolves back to "localhost" (as some applications assume) instead of # Note: localhostHosts has to appear first in /etc/hosts so that 127.0.0.1
# the FQDN! By default "networking.hosts" also contains entries for the # resolves back to "localhost" (as some applications assume) instead of
# FQDN so that e.g. "hostname -f" works correctly. # the FQDN! By default "networking.hosts" also contains entries for the
localhostHosts = pkgs.writeText "localhost-hosts" '' # FQDN so that e.g. "hostname -f" works correctly.
127.0.0.1 localhost localhostHosts = pkgs.writeText "localhost-hosts" ''
${lib.optionalString cfg.enableIPv6 "::1 localhost"} 127.0.0.1 localhost
''; ${lib.optionalString cfg.enableIPv6 "::1 localhost"}
stringHosts = '';
let stringHosts =
oneToString = set: ip: ip + " " + lib.concatStringsSep " " set.${ip} + "\n"; let
allToString = set: lib.concatMapStrings (oneToString set) (lib.attrNames set); oneToString = set: ip: ip + " " + lib.concatStringsSep " " set.${ip} + "\n";
in pkgs.writeText "string-hosts" (allToString (lib.filterAttrs (_: v: v != []) cfg.hosts)); allToString = set: lib.concatMapStrings (oneToString set) (lib.attrNames set);
extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts; in
in lib.mkBefore [ localhostHosts stringHosts extraHosts ]; pkgs.writeText "string-hosts" (allToString (lib.filterAttrs (_: v: v != [ ]) cfg.hosts));
extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts;
in
lib.mkBefore [
localhostHosts
stringHosts
extraHosts
];
environment.etc = environment.etc =
{ # /etc/services: TCP/UDP port assignments. {
# /etc/services: TCP/UDP port assignments.
services.source = pkgs.iana-etc + "/etc/services"; services.source = pkgs.iana-etc + "/etc/services";
# /etc/protocols: IP protocol numbers. # /etc/protocols: IP protocol numbers.
protocols.source = pkgs.iana-etc + "/etc/protocols"; protocols.source = pkgs.iana-etc + "/etc/protocols";
# /etc/hosts: Hostname-to-IP mappings. # /etc/hosts: Hostname-to-IP mappings.
hosts.source = pkgs.concatText "hosts" cfg.hostFiles; hosts.source = pkgs.concatText "hosts" cfg.hostFiles;
@ -202,28 +228,35 @@ in
multi on multi on
''; '';
} // lib.optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") { }
// lib.optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") {
# /etc/rpc: RPC program numbers. # /etc/rpc: RPC program numbers.
rpc.source = pkgs.stdenv.cc.libc.out + "/etc/rpc"; rpc.source = pkgs.stdenv.cc.libc.out + "/etc/rpc";
}; };
networking.proxy.envVars = networking.proxy.envVars =
lib.optionalAttrs (cfg.proxy.default != null) { lib.optionalAttrs (cfg.proxy.default != null) {
# other options already fallback to proxy.default # other options already fallback to proxy.default
no_proxy = "127.0.0.1,localhost"; no_proxy = "127.0.0.1,localhost";
} // lib.optionalAttrs (cfg.proxy.httpProxy != null) { }
http_proxy = cfg.proxy.httpProxy; // lib.optionalAttrs (cfg.proxy.httpProxy != null) {
} // lib.optionalAttrs (cfg.proxy.httpsProxy != null) { http_proxy = cfg.proxy.httpProxy;
https_proxy = cfg.proxy.httpsProxy; }
} // lib.optionalAttrs (cfg.proxy.rsyncProxy != null) { // lib.optionalAttrs (cfg.proxy.httpsProxy != null) {
rsync_proxy = cfg.proxy.rsyncProxy; https_proxy = cfg.proxy.httpsProxy;
} // lib.optionalAttrs (cfg.proxy.ftpProxy != null) { }
ftp_proxy = cfg.proxy.ftpProxy; // lib.optionalAttrs (cfg.proxy.rsyncProxy != null) {
} // lib.optionalAttrs (cfg.proxy.allProxy != null) { rsync_proxy = cfg.proxy.rsyncProxy;
all_proxy = cfg.proxy.allProxy; }
} // lib.optionalAttrs (cfg.proxy.noProxy != null) { // lib.optionalAttrs (cfg.proxy.ftpProxy != null) {
no_proxy = cfg.proxy.noProxy; ftp_proxy = cfg.proxy.ftpProxy;
}; }
// lib.optionalAttrs (cfg.proxy.allProxy != null) {
all_proxy = cfg.proxy.allProxy;
}
// lib.optionalAttrs (cfg.proxy.noProxy != null) {
no_proxy = cfg.proxy.noProxy;
};
# Install the proxy environment variables # Install the proxy environment variables
environment.sessionVariables = cfg.proxy.envVars; environment.sessionVariables = cfg.proxy.envVars;

View file

@ -5,7 +5,7 @@
See also See also
- ./nix.nix - ./nix.nix
- ./nix-flakes.nix - ./nix-flakes.nix
*/ */
{ config, lib, ... }: { config, lib, ... }:
let let
inherit (lib) inherit (lib)
@ -42,13 +42,14 @@ in
nixPath = mkOption { nixPath = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = default =
if cfg.channel.enable if cfg.channel.enable then
then [ [
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos" "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix" "nixos-config=/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels" "/nix/var/nix/profiles/per-user/root/channels"
] ]
else [ ]; else
[ ];
defaultText = '' defaultText = ''
if nix.channel.enable if nix.channel.enable
then [ then [
@ -78,12 +79,11 @@ in
config = mkIf cfg.enable { config = mkIf cfg.enable {
environment.extraInit = environment.extraInit = mkIf cfg.channel.enable ''
mkIf cfg.channel.enable '' if [ -e "$HOME/.nix-defexpr/channels" ]; then
if [ -e "$HOME/.nix-defexpr/channels" ]; then export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}" fi
fi '';
'';
environment.extraSetup = mkIf (!cfg.channel.enable) '' environment.extraSetup = mkIf (!cfg.channel.enable) ''
rm --force $out/bin/nix-channel rm --force $out/bin/nix-channel
@ -99,7 +99,8 @@ in
''f /root/.nix-channels - - - - ${config.system.defaultChannel} nixos\n'' ''f /root/.nix-channels - - - - ${config.system.defaultChannel} nixos\n''
]; ];
system.activationScripts.no-nix-channel = mkIf (!cfg.channel.enable) system.activationScripts.no-nix-channel = mkIf (!cfg.channel.enable) (
(stringAfter [ "etc" "users" ] (builtins.readFile ./nix-channel/activation-check.sh)); stringAfter [ "etc" "users" ] (builtins.readFile ./nix-channel/activation-check.sh)
);
}; };
} }

View file

@ -6,8 +6,13 @@
- ./nix-flakes.nix - ./nix-flakes.nix
- ./nix-remote-build.nix - ./nix-remote-build.nix
- nixos/modules/services/system/nix-daemon.nix - nixos/modules/services/system/nix-daemon.nix
*/ */
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (lib) inherit (lib)
@ -61,19 +66,21 @@ let
systemFeatures = "system-features"; systemFeatures = "system-features";
}; };
semanticConfType = with types; semanticConfType =
with types;
let let
confAtom = nullOr confAtom =
(oneOf [ nullOr (oneOf [
bool bool
int int
float float
str str
path path
package package
]) // { ])
description = "Nix config atom (null, bool, int, float, str, path or package)"; // {
}; description = "Nix config atom (null, bool, int, float, str, path or package)";
};
in in
attrsOf (either confAtom (listOf confAtom)); attrsOf (either confAtom (listOf confAtom));
@ -81,17 +88,28 @@ let
assert isNixAtLeast "2.2"; assert isNixAtLeast "2.2";
let let
mkValueString = v: mkValueString =
if v == null then "" v:
else if isInt v then toString v if v == null then
else if isBool v then boolToString v ""
else if isFloat v then floatToString v else if isInt v then
else if isList v then toString v toString v
else if isDerivation v then toString v else if isBool v then
else if builtins.isPath v then toString v boolToString v
else if isString v then v else if isFloat v then
else if strings.isConvertibleWithToString v then toString v floatToString v
else abort "The nix conf value: ${toPretty {} v} can not be encoded"; else if isList v then
toString v
else if isDerivation v then
toString v
else if builtins.isPath v then
toString v
else if isString v then
v
else if strings.isConvertibleWithToString v then
toString v
else
abort "The nix conf value: ${toPretty { } v} can not be encoded";
mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}";
@ -113,41 +131,71 @@ let
${cfg.extraOptions} ${cfg.extraOptions}
''; '';
checkPhase = lib.optionalString cfg.checkConfig ( checkPhase = lib.optionalString cfg.checkConfig (
if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then '' if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then
echo "Ignoring validation for cross-compilation" ''
'' echo "Ignoring validation for cross-compilation"
''
else else
let let
showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config"; showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
in in
'' ''
echo "Validating generated nix.conf" echo "Validating generated nix.conf"
ln -s $out ./nix.conf ln -s $out ./nix.conf
set -e set -e
set +o pipefail set +o pipefail
NIX_CONF_DIR=$PWD \ NIX_CONF_DIR=$PWD \
${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \ ${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \ ${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
|& sed -e 's/^warning:/error:/' \ |& sed -e 's/^warning:/error:/' \
| (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}') | (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}')
set -o pipefail set -o pipefail
''); ''
);
}; };
in in
{ {
imports = [ imports =
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; }) [
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; }) (mkRenamedOptionModuleWith {
] ++ sinceRelease = 2003;
mapAttrsToList from = [
(oldConf: newConf: "nix"
mkRenamedOptionModuleWith { "useChroot"
sinceRelease = 2205; ];
from = [ "nix" oldConf ]; to = [
to = [ "nix" "settings" newConf ]; "nix"
"useSandbox"
];
}) })
legacyConfMappings; (mkRenamedOptionModuleWith {
sinceRelease = 2003;
from = [
"nix"
"chrootDirs"
];
to = [
"nix"
"sandboxPaths"
];
})
]
++ mapAttrsToList (
oldConf: newConf:
mkRenamedOptionModuleWith {
sinceRelease = 2205;
from = [
"nix"
oldConf
];
to = [
"nix"
"settings"
newConf
];
}
) legacyConfMappings;
options = { options = {
nix = { nix = {
@ -246,7 +294,10 @@ in
extra-sandbox-paths = mkOption { extra-sandbox-paths = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [ ];
example = [ "/dev" "/proc" ]; example = [
"/dev"
"/proc"
];
description = '' description = ''
Directories from the host filesystem to be included Directories from the host filesystem to be included
in the sandbox. in the sandbox.
@ -302,7 +353,11 @@ in
trusted-users = mkOption { trusted-users = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
example = [ "root" "alice" "@wheel" ]; example = [
"root"
"alice"
"@wheel"
];
description = '' description = ''
A list of names of users that have additional rights when A list of names of users that have additional rights when
connecting to the Nix daemon, such as the ability to specify connecting to the Nix daemon, such as the ability to specify
@ -316,7 +371,11 @@ in
system-features = mkOption { system-features = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
example = [ "kvm" "big-parallel" "gccarch-skylake" ]; example = [
"kvm"
"big-parallel"
"gccarch-skylake"
];
description = '' description = ''
The set of features supported by the machine. Derivations The set of features supported by the machine. Derivations
can express dependencies on system features through the can express dependencies on system features through the
@ -331,7 +390,12 @@ in
allowed-users = mkOption { allowed-users = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ "*" ]; default = [ "*" ];
example = [ "@wheel" "@builders" "alice" "bob" ]; example = [
"@wheel"
"@builders"
"alice"
"bob"
];
description = '' description = ''
A list of names of users (separated by whitespace) that are A list of names of users (separated by whitespace) that are
allowed to connect to the Nix daemon. As with allowed to connect to the Nix daemon. As with
@ -378,11 +442,18 @@ in
trusted-users = [ "root" ]; trusted-users = [ "root" ];
substituters = mkAfter [ "https://cache.nixos.org/" ]; substituters = mkAfter [ "https://cache.nixos.org/" ];
system-features = mkDefault ( system-features = mkDefault (
[ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++ [
optionals (pkgs.stdenv.hostPlatform ? gcc.arch) ( "nixos-test"
"benchmark"
"big-parallel"
"kvm"
]
++ optionals (pkgs.stdenv.hostPlatform ? gcc.arch) (
# a builder can run code for `gcc.arch` and inferior architectures # a builder can run code for `gcc.arch` and inferior architectures
[ "gccarch-${pkgs.stdenv.hostPlatform.gcc.arch}" ] ++ [ "gccarch-${pkgs.stdenv.hostPlatform.gcc.arch}" ]
map (x: "gccarch-${x}") (systems.architectures.inferiors.${pkgs.stdenv.hostPlatform.gcc.arch} or []) ++ map (x: "gccarch-${x}") (
systems.architectures.inferiors.${pkgs.stdenv.hostPlatform.gcc.arch} or [ ]
)
) )
); );
}; };

View file

@ -1,27 +1,35 @@
# This module defines a global environment configuration and # This module defines a global environment configuration and
# a common configuration for all shells. # a common configuration for all shells.
{ config, lib, utils, pkgs, ... }: {
config,
lib,
utils,
pkgs,
...
}:
let let
cfg = config.environment; cfg = config.environment;
exportedEnvVars = exportedEnvVars =
let let
absoluteVariables = absoluteVariables = lib.mapAttrs (n: lib.toList) cfg.variables;
lib.mapAttrs (n: lib.toList) cfg.variables;
suffixedVariables = suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeEnvVars (
lib.flip lib.mapAttrs cfg.profileRelativeEnvVars (envVar: listSuffixes: envVar: listSuffixes:
lib.concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles lib.concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles
); );
allVariables = allVariables = lib.zipAttrsWith (n: lib.concatLists) [
lib.zipAttrsWith (n: lib.concatLists) [ absoluteVariables suffixedVariables ]; absoluteVariables
suffixedVariables
];
exportVariables = exportVariables = lib.mapAttrsToList (
lib.mapAttrsToList (n: v: ''export ${n}="${lib.concatStringsSep ":" v}"'') allVariables; n: v: ''export ${n}="${lib.concatStringsSep ":" v}"''
) allVariables;
in in
lib.concatStringsSep "\n" exportVariables; lib.concatStringsSep "\n" exportVariables;
in in
{ {
@ -29,8 +37,11 @@ in
options = { options = {
environment.variables = lib.mkOption { environment.variables = lib.mkOption {
default = {}; default = { };
example = { EDITOR = "nvim"; VISUAL = "nvim"; }; example = {
EDITOR = "nvim";
VISUAL = "nvim";
};
description = '' description = ''
A set of environment variables used in the global environment. A set of environment variables used in the global environment.
These variables will be set on shell initialisation (e.g. in /etc/profile). These variables will be set on shell initialisation (e.g. in /etc/profile).
@ -38,14 +49,27 @@ in
strings. The latter is concatenated, interspersed with colon strings. The latter is concatenated, interspersed with colon
characters. characters.
''; '';
type = with lib.types; attrsOf (oneOf [ (listOf (oneOf [ int str path ])) int str path ]); type =
apply = let with lib.types;
toStr = v: if lib.isPath v then "${v}" else toString v; attrsOf (oneOf [
in lib.mapAttrs (n: v: if lib.isList v then lib.concatMapStringsSep ":" toStr v else toStr v); (listOf (oneOf [
int
str
path
]))
int
str
path
]);
apply =
let
toStr = v: if lib.isPath v then "${v}" else toString v;
in
lib.mapAttrs (n: v: if lib.isList v then lib.concatMapStringsSep ":" toStr v else toStr v);
}; };
environment.profiles = lib.mkOption { environment.profiles = lib.mkOption {
default = []; default = [ ];
description = '' description = ''
A list of profiles used to setup the global environment. A list of profiles used to setup the global environment.
''; '';
@ -54,7 +78,13 @@ in
environment.profileRelativeEnvVars = lib.mkOption { environment.profileRelativeEnvVars = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str); type = lib.types.attrsOf (lib.types.listOf lib.types.str);
example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; }; example = {
PATH = [ "/bin" ];
MANPATH = [
"/man"
"/share/man"
];
};
description = '' description = ''
Attribute set of environment variable. Each attribute maps to a list Attribute set of environment variable. Each attribute maps to a list
of relative paths. Each relative path is appended to the each profile of relative paths. Each relative path is appended to the each profile
@ -106,7 +136,10 @@ in
}; };
environment.shellAliases = lib.mkOption { environment.shellAliases = lib.mkOption {
example = { l = null; ll = "ls -l"; }; example = {
l = null;
ll = "ls -l";
};
description = '' description = ''
An attribute set that maps aliases (the top level attribute names in An attribute set that maps aliases (the top level attribute names in
this option) to command strings or directly to build outputs. The this option) to command strings or directly to build outputs. The
@ -147,7 +180,7 @@ in
}; };
environment.shells = lib.mkOption { environment.shells = lib.mkOption {
default = []; default = [ ];
example = lib.literalExpression "[ pkgs.bashInteractive pkgs.zsh ]"; example = lib.literalExpression "[ pkgs.bashInteractive pkgs.zsh ]";
description = '' description = ''
A list of permissible login shells for user accounts. A list of permissible login shells for user accounts.
@ -174,49 +207,46 @@ in
environment.shellAliases = lib.mapAttrs (name: lib.mkDefault) { environment.shellAliases = lib.mapAttrs (name: lib.mkDefault) {
ls = "ls --color=tty"; ls = "ls --color=tty";
ll = "ls -l"; ll = "ls -l";
l = "ls -alh"; l = "ls -alh";
}; };
environment.etc.shells.text = environment.etc.shells.text = ''
'' ${lib.concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
${lib.concatStringsSep "\n" (map utils.toShellPath cfg.shells)} /bin/sh
/bin/sh '';
'';
# For resetting environment with `. /etc/set-environment` when needed # For resetting environment with `. /etc/set-environment` when needed
# and discoverability (see motivation of #30418). # and discoverability (see motivation of #30418).
environment.etc.set-environment.source = config.system.build.setEnvironment; environment.etc.set-environment.source = config.system.build.setEnvironment;
system.build.setEnvironment = pkgs.writeText "set-environment" system.build.setEnvironment = pkgs.writeText "set-environment" ''
'' # DO NOT EDIT -- this file has been generated automatically.
# DO NOT EDIT -- this file has been generated automatically.
# Prevent this file from being sourced by child shells. # Prevent this file from being sourced by child shells.
export __NIXOS_SET_ENVIRONMENT_DONE=1 export __NIXOS_SET_ENVIRONMENT_DONE=1
${exportedEnvVars} ${exportedEnvVars}
${cfg.extraInit} ${cfg.extraInit}
${lib.optionalString cfg.homeBinInPath '' ${lib.optionalString cfg.homeBinInPath ''
# ~/bin if it exists overrides other bin directories. # ~/bin if it exists overrides other bin directories.
export PATH="$HOME/bin:$PATH" export PATH="$HOME/bin:$PATH"
''} ''}
${lib.optionalString cfg.localBinInPath '' ${lib.optionalString cfg.localBinInPath ''
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
''} ''}
''; '';
system.activationScripts.binsh = lib.stringAfter [ "stdio" ] system.activationScripts.binsh = lib.stringAfter [ "stdio" ] ''
'' # Create the required /bin/sh symlink; otherwise lots of things
# Create the required /bin/sh symlink; otherwise lots of things # (notably the system() function) won't work.
# (notably the system() function) won't work. mkdir -p /bin
mkdir -p /bin chmod 0755 /bin
chmod 0755 /bin ln -sfn "${cfg.binsh}" /bin/.sh.tmp
ln -sfn "${cfg.binsh}" /bin/.sh.tmp mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh
mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh '';
'';
}; };

View file

@ -1,196 +1,212 @@
{ config, lib, pkgs, utils, ... }: {
config,
lib,
pkgs,
utils,
...
}:
let let
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkOption types;
randomEncryptionCoerce = enable: { inherit enable; }; randomEncryptionCoerce = enable: { inherit enable; };
randomEncryptionOpts = { ... }: { randomEncryptionOpts =
{ ... }:
{
options = { options = {
enable = mkOption { enable = mkOption {
default = false; default = false;
type = types.bool; type = types.bool;
description = '' description = ''
Encrypt swap device with a random key. This way you won't have a persistent swap device. Encrypt swap device with a random key. This way you won't have a persistent swap device.
WARNING: Don't try to hibernate when you have at least one swap partition with WARNING: Don't try to hibernate when you have at least one swap partition with
this option enabled! We have no way to set the partition into which hibernation image this option enabled! We have no way to set the partition into which hibernation image
is saved, so if your image ends up on an encrypted one you would lose it! is saved, so if your image ends up on an encrypted one you would lose it!
WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
when using randomEncryption as the UUIDs and labels will get erased on every boot when when using randomEncryption as the UUIDs and labels will get erased on every boot when
the partition is encrypted. Best to use /dev/disk/by-partuuid/ the partition is encrypted. Best to use /dev/disk/by-partuuid/
''; '';
};
cipher = mkOption {
default = "aes-xts-plain64";
example = "serpent-xts-plain64";
type = types.str;
description = ''
Use specified cipher for randomEncryption.
Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
'';
};
keySize = mkOption {
default = null;
example = "512";
type = types.nullOr types.int;
description = ''
Set the encryption key size for the plain device.
If not specified, the amount of data to read from `source` will be
determined by cryptsetup.
See `cryptsetup-open(8)` for details.
'';
};
sectorSize = mkOption {
default = null;
example = "4096";
type = types.nullOr types.int;
description = ''
Set the sector size for the plain encrypted device type.
If not specified, the default sector size is determined from the
underlying block device.
See `cryptsetup-open(8)` for details.
'';
};
source = mkOption {
default = "/dev/urandom";
example = "/dev/random";
type = types.str;
description = ''
Define the source of randomness to obtain a random key for encryption.
'';
};
allowDiscards = mkOption {
default = false;
type = types.bool;
description = ''
Whether to allow TRIM requests to the underlying device. This option
has security implications; please read the LUKS documentation before
activating it.
'';
};
};
};
swapCfg = {config, options, ...}: {
options = {
device = mkOption {
example = "/dev/sda3";
type = types.nonEmptyStr;
description = "Path of the device or swap file.";
};
label = mkOption {
example = "swap";
type = types.str;
description = ''
Label of the device. Can be used instead of {var}`device`.
'';
};
size = mkOption {
default = null;
example = 2048;
type = types.nullOr types.int;
description = ''
If this option is set, device is interpreted as the
path of a swapfile that will be created automatically
with the indicated size (in megabytes).
'';
};
priority = mkOption {
default = null;
example = 2048;
type = types.nullOr types.int;
description = ''
Specify the priority of the swap device. Priority is a value between 0 and 32767.
Higher numbers indicate higher priority.
null lets the kernel choose a priority, which will show up as a negative value.
'';
};
randomEncryption = mkOption {
default = false;
example = {
enable = true;
cipher = "serpent-xts-plain64";
source = "/dev/random";
}; };
type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
description = ''
Encrypt swap device with a random key. This way you won't have a persistent swap device.
HINT: run "cryptsetup benchmark" to test cipher performance on your machine. cipher = mkOption {
default = "aes-xts-plain64";
example = "serpent-xts-plain64";
type = types.str;
description = ''
Use specified cipher for randomEncryption.
WARNING: Don't try to hibernate when you have at least one swap partition with Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
this option enabled! We have no way to set the partition into which hibernation image '';
is saved, so if your image ends up on an encrypted one you would lose it! };
WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device keySize = mkOption {
when using randomEncryption as the UUIDs and labels will get erased on every boot when default = null;
the partition is encrypted. Best to use /dev/disk/by-partuuid/ example = "512";
''; type = types.nullOr types.int;
}; description = ''
Set the encryption key size for the plain device.
discardPolicy = mkOption { If not specified, the amount of data to read from `source` will be
default = null; determined by cryptsetup.
example = "once";
type = types.nullOr (types.enum ["once" "pages" "both" ]);
description = ''
Specify the discard policy for the swap device. If "once", then the
whole swap space is discarded at swapon invocation. If "pages",
asynchronous discard on freed pages is performed, before returning to
the available pages pool. With "both", both policies are activated.
See swapon(8) for more information.
'';
};
options = mkOption { See `cryptsetup-open(8)` for details.
default = [ "defaults" ]; '';
example = [ "nofail" ]; };
type = types.listOf types.nonEmptyStr;
description = ''
Options used to mount the swap.
'';
};
deviceName = mkOption { sectorSize = mkOption {
type = types.str; default = null;
internal = true; example = "4096";
}; type = types.nullOr types.int;
description = ''
Set the sector size for the plain encrypted device type.
realDevice = mkOption { If not specified, the default sector size is determined from the
type = types.path; underlying block device.
internal = true;
See `cryptsetup-open(8)` for details.
'';
};
source = mkOption {
default = "/dev/urandom";
example = "/dev/random";
type = types.str;
description = ''
Define the source of randomness to obtain a random key for encryption.
'';
};
allowDiscards = mkOption {
default = false;
type = types.bool;
description = ''
Whether to allow TRIM requests to the underlying device. This option
has security implications; please read the LUKS documentation before
activating it.
'';
};
}; };
}; };
config = { swapCfg =
device = mkIf options.label.isDefined { config, options, ... }:
"/dev/disk/by-label/${config.label}"; {
deviceName = lib.replaceStrings ["\\"] [""] (utils.escapeSystemdPath config.device);
realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
};
}; options = {
device = mkOption {
example = "/dev/sda3";
type = types.nonEmptyStr;
description = "Path of the device or swap file.";
};
label = mkOption {
example = "swap";
type = types.str;
description = ''
Label of the device. Can be used instead of {var}`device`.
'';
};
size = mkOption {
default = null;
example = 2048;
type = types.nullOr types.int;
description = ''
If this option is set, device is interpreted as the
path of a swapfile that will be created automatically
with the indicated size (in megabytes).
'';
};
priority = mkOption {
default = null;
example = 2048;
type = types.nullOr types.int;
description = ''
Specify the priority of the swap device. Priority is a value between 0 and 32767.
Higher numbers indicate higher priority.
null lets the kernel choose a priority, which will show up as a negative value.
'';
};
randomEncryption = mkOption {
default = false;
example = {
enable = true;
cipher = "serpent-xts-plain64";
source = "/dev/random";
};
type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
description = ''
Encrypt swap device with a random key. This way you won't have a persistent swap device.
HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
WARNING: Don't try to hibernate when you have at least one swap partition with
this option enabled! We have no way to set the partition into which hibernation image
is saved, so if your image ends up on an encrypted one you would lose it!
WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
when using randomEncryption as the UUIDs and labels will get erased on every boot when
the partition is encrypted. Best to use /dev/disk/by-partuuid/
'';
};
discardPolicy = mkOption {
default = null;
example = "once";
type = types.nullOr (
types.enum [
"once"
"pages"
"both"
]
);
description = ''
Specify the discard policy for the swap device. If "once", then the
whole swap space is discarded at swapon invocation. If "pages",
asynchronous discard on freed pages is performed, before returning to
the available pages pool. With "both", both policies are activated.
See swapon(8) for more information.
'';
};
options = mkOption {
default = [ "defaults" ];
example = [ "nofail" ];
type = types.listOf types.nonEmptyStr;
description = ''
Options used to mount the swap.
'';
};
deviceName = mkOption {
type = types.str;
internal = true;
};
realDevice = mkOption {
type = types.path;
internal = true;
};
};
config = {
device = mkIf options.label.isDefined "/dev/disk/by-label/${config.label}";
deviceName = lib.replaceStrings [ "\\" ] [ "" ] (utils.escapeSystemdPath config.device);
realDevice =
if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
};
};
in in
@ -201,7 +217,7 @@ in
options = { options = {
swapDevices = mkOption { swapDevices = mkOption {
default = []; default = [ ];
example = [ example = [
{ device = "/dev/hda7"; } { device = "/dev/hda7"; }
{ device = "/var/swapfile"; } { device = "/var/swapfile"; }
@ -224,7 +240,8 @@ in
config = mkIf ((lib.length config.swapDevices) != 0) { config = mkIf ((lib.length config.swapDevices) != 0) {
assertions = lib.map (sw: { assertions = lib.map (sw: {
assertion = sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null; assertion =
sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
message = '' message = ''
You cannot use swap device "${sw.device}" with randomEncryption enabled. You cannot use swap device "${sw.device}" with randomEncryption enabled.
The UUIDs and labels will get erased on every boot when the partition is encrypted. The UUIDs and labels will get erased on every boot when the partition is encrypted.
@ -232,12 +249,13 @@ in
''; '';
}) config.swapDevices; }) config.swapDevices;
warnings = warnings = lib.concatMap (
lib.concatMap (sw: sw:
if sw.size != null && lib.hasPrefix "/dev/" sw.device if sw.size != null && lib.hasPrefix "/dev/" sw.device then
then [ "Setting the swap size of block device ${sw.device} has no effect" ] [ "Setting the swap size of block device ${sw.device} has no effect" ]
else [ ]) else
config.swapDevices; [ ]
) config.swapDevices;
system.requiredKernelConfig = [ system.requiredKernelConfig = [
(config.lib.kernelConfig.isYes "SWAP") (config.lib.kernelConfig.isYes "SWAP")
@ -246,47 +264,62 @@ in
# Create missing swapfiles. # Create missing swapfiles.
systemd.services = systemd.services =
let let
createSwapDevice = sw: createSwapDevice =
let realDevice' = utils.escapeSystemdPath sw.realDevice; sw:
in lib.nameValuePair "mkswap-${sw.deviceName}" let
{ description = "Initialisation of swap device ${sw.device}"; realDevice' = utils.escapeSystemdPath sw.realDevice;
in
lib.nameValuePair "mkswap-${sw.deviceName}" {
description = "Initialisation of swap device ${sw.device}";
# The mkswap service fails for file-backed swap devices if the # The mkswap service fails for file-backed swap devices if the
# loop module has not been loaded before the service runs. # loop module has not been loaded before the service runs.
# We add an ordering constraint to run after systemd-modules-load to # We add an ordering constraint to run after systemd-modules-load to
# avoid this race condition. # avoid this race condition.
after = [ "systemd-modules-load.service" ]; after = [ "systemd-modules-load.service" ];
wantedBy = [ "${realDevice'}.swap" ]; wantedBy = [ "${realDevice'}.swap" ];
before = [ "${realDevice'}.swap" "shutdown.target"]; before = [
"${realDevice'}.swap"
"shutdown.target"
];
conflicts = [ "shutdown.target" ]; conflicts = [ "shutdown.target" ];
path = [ pkgs.util-linux pkgs.e2fsprogs ] path = [
++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup; pkgs.util-linux
pkgs.e2fsprogs
] ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup;
environment.DEVICE = sw.device; environment.DEVICE = sw.device;
script = script = ''
'' ${lib.optionalString (sw.size != null) ''
${lib.optionalString (sw.size != null) '' currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 )) if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then # Disable CoW for CoW based filesystems like BTRFS.
# Disable CoW for CoW based filesystems like BTRFS. truncate --size 0 "$DEVICE"
truncate --size 0 "$DEVICE" chattr +C "$DEVICE" 2>/dev/null || true
chattr +C "$DEVICE" 2>/dev/null || true
echo "Creating swap file using dd and mkswap." echo "Creating swap file using dd and mkswap."
dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress
${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"} ${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
fi fi
''} ''}
${lib.optionalString sw.randomEncryption.enable '' ${lib.optionalString sw.randomEncryption.enable ''
cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \ cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
${lib.concatStringsSep " \\\n" (lib.flatten [ ${
(lib.optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}") lib.concatStringsSep " \\\n" (
(lib.optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}") lib.flatten [
(lib.optional sw.randomEncryption.allowDiscards "--allow-discards") (lib.optional (
])} ${sw.device} ${sw.deviceName} sw.randomEncryption.sectorSize != null
mkswap ${sw.realDevice} ) "--sector-size=${toString sw.randomEncryption.sectorSize}")
''} (lib.optional (
''; sw.randomEncryption.keySize != null
) "--key-size=${toString sw.randomEncryption.keySize}")
(lib.optional sw.randomEncryption.allowDiscards "--allow-discards")
]
)
} ${sw.device} ${sw.deviceName}
mkswap ${sw.realDevice}
''}
'';
unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ]; unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ];
unitConfig.DefaultDependencies = false; # needed to prevent a cycle unitConfig.DefaultDependencies = false; # needed to prevent a cycle
@ -299,7 +332,12 @@ in
restartIfChanged = false; restartIfChanged = false;
}; };
in lib.listToAttrs (lib.map createSwapDevice (lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices)); in
lib.listToAttrs (
lib.map createSwapDevice (
lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices
)
);
}; };

View file

@ -1,6 +1,12 @@
# This module defines a system-wide environment that will be # This module defines a system-wide environment that will be
# initialised by pam_env (that is, not only in shells). # initialised by pam_env (that is, not only in shells).
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
let let
cfg = config.environment; cfg = config.environment;
@ -12,7 +18,7 @@ in
options = { options = {
environment.sessionVariables = lib.mkOption { environment.sessionVariables = lib.mkOption {
default = {}; default = { };
description = '' description = ''
A set of environment variables used in the global environment. A set of environment variables used in the global environment.
These variables will be set by PAM early in the login process. These variables will be set by PAM early in the login process.
@ -34,7 +40,13 @@ in
environment.profileRelativeSessionVariables = lib.mkOption { environment.profileRelativeSessionVariables = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str); type = lib.types.attrsOf (lib.types.listOf lib.types.str);
example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; }; example = {
PATH = [ "/bin" ];
MANPATH = [
"/man"
"/share/man"
];
};
description = '' description = ''
Attribute set of environment variable used in the global Attribute set of environment variable used in the global
environment. These variables will be set by PAM early in the environment. These variables will be set by PAM early in the
@ -58,40 +70,40 @@ in
}; };
config = { config = {
environment.etc."pam/environment".text = let environment.etc."pam/environment".text =
suffixedVariables = let
lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (envVar: suffixes: suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (
lib.flip lib.concatMap cfg.profiles (profile: envVar: suffixes:
map (suffix: "${profile}${suffix}") 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. # 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 # That means we need to map the env variables that people might use to their
# equivalent PAM variable. # equivalent PAM variable.
replaceEnvVars = lib.replaceStrings ["$HOME" "$USER"] ["@{HOME}" "@{PAM_USER}"]; replaceEnvVars = lib.replaceStrings [ "$HOME" "$USER" ] [ "@{HOME}" "@{PAM_USER}" ];
pamVariable = n: v: pamVariable =
''${n} DEFAULT="${lib.concatStringsSep ":" (map replaceEnvVars (lib.toList v))}"''; n: v: ''${n} DEFAULT="${lib.concatStringsSep ":" (map replaceEnvVars (lib.toList v))}"'';
pamVariables = pamVariables = lib.concatStringsSep "\n" (
lib.concatStringsSep "\n" lib.mapAttrsToList pamVariable (
(lib.mapAttrsToList pamVariable lib.zipAttrsWith (n: lib.concatLists) [
(lib.zipAttrsWith (n: lib.concatLists) # Make sure security wrappers are prioritized without polluting
[ # shell environments with an extra entry. Sessions which depend on
# Make sure security wrappers are prioritized without polluting # pam for its environment will otherwise have eg. broken sudo. In
# shell environments with an extra entry. Sessions which depend on # particular Gnome Shell sometimes fails to source a proper
# pam for its environment will otherwise have eg. broken sudo. In # environment from a shell.
# particular Gnome Shell sometimes fails to source a proper { PATH = [ config.security.wrapperDir ]; }
# environment from a shell.
{ PATH = [ config.security.wrapperDir ]; }
(lib.mapAttrs (n: lib.toList) cfg.sessionVariables) (lib.mapAttrs (n: lib.toList) cfg.sessionVariables)
suffixedVariables suffixedVariables
])); ]
in '' )
${pamVariables} );
''; in
''
${pamVariables}
'';
}; };
} }

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,14 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.xdg.mime; cfg = config.xdg.mime;
associationOptions = with lib.types; attrsOf ( associationOptions =
coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str with lib.types;
); attrsOf (coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str);
in in
{ {
@ -24,10 +29,13 @@ in
xdg.mime.addedAssociations = lib.mkOption { xdg.mime.addedAssociations = lib.mkOption {
type = associationOptions; type = associationOptions;
default = {}; default = { };
example = { example = {
"application/pdf" = "firefox.desktop"; "application/pdf" = "firefox.desktop";
"text/xml" = [ "nvim.desktop" "codium.desktop" ]; "text/xml" = [
"nvim.desktop"
"codium.desktop"
];
}; };
description = '' description = ''
Adds associations between mimetypes and applications. See the Adds associations between mimetypes and applications. See the
@ -38,10 +46,13 @@ in
xdg.mime.defaultApplications = lib.mkOption { xdg.mime.defaultApplications = lib.mkOption {
type = associationOptions; type = associationOptions;
default = {}; default = { };
example = { example = {
"application/pdf" = "firefox.desktop"; "application/pdf" = "firefox.desktop";
"image/png" = [ "sxiv.desktop" "gimp.desktop" ]; "image/png" = [
"sxiv.desktop"
"gimp.desktop"
];
}; };
description = '' description = ''
Sets the default applications for given mimetypes. See the Sets the default applications for given mimetypes. See the
@ -52,9 +63,12 @@ in
xdg.mime.removedAssociations = lib.mkOption { xdg.mime.removedAssociations = lib.mkOption {
type = associationOptions; type = associationOptions;
default = {}; default = { };
example = { example = {
"audio/mp3" = [ "mpv.desktop" "umpv.desktop" ]; "audio/mp3" = [
"mpv.desktop"
"umpv.desktop"
];
"inode/directory" = "codium.desktop"; "inode/directory" = "codium.desktop";
}; };
description = '' description = ''
@ -66,17 +80,16 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
environment.etc."xdg/mimeapps.list" = lib.mkIf ( environment.etc."xdg/mimeapps.list" =
cfg.addedAssociations != {} lib.mkIf
|| cfg.defaultApplications != {} (cfg.addedAssociations != { } || cfg.defaultApplications != { } || cfg.removedAssociations != { })
|| cfg.removedAssociations != {} {
) { text = lib.generators.toINI { } {
text = lib.generators.toINI { } { "Added Associations" = cfg.addedAssociations;
"Added Associations" = cfg.addedAssociations; "Default Applications" = cfg.defaultApplications;
"Default Applications" = cfg.defaultApplications; "Removed Associations" = cfg.removedAssociations;
"Removed Associations" = cfg.removedAssociations; };
}; };
};
environment.pathsToLink = [ "/share/mime" ]; environment.pathsToLink = [ "/share/mime" ];

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -10,7 +15,10 @@ in
{ {
imports = [ imports = [
(lib.mkRemovedOptionModule [ "zramSwap" "numDevices" ] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported") (lib.mkRemovedOptionModule [
"zramSwap"
"numDevices"
] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported")
]; ];
###### interface ###### interface
@ -73,7 +81,16 @@ in
algorithm = lib.mkOption { algorithm = lib.mkOption {
default = "zstd"; default = "zstd";
example = "lz4"; example = "lz4";
type = with lib.types; either (enum [ "842" "lzo" "lzo-rle" "lz4" "lz4hc" "zstd" ]) str; type =
with lib.types;
either (enum [
"842"
"lzo"
"lzo-rle"
"lz4"
"lz4hc"
"zstd"
]) str;
description = '' description = ''
Compression algorithm. `lzo` has good compression, Compression algorithm. `lzo` has good compression,
but is slow. `lz4` has bad compression, but is fast. but is slow. `lz4` has bad compression, but is fast.
@ -107,23 +124,24 @@ in
services.zram-generator.enable = true; services.zram-generator.enable = true;
services.zram-generator.settings = lib.listToAttrs services.zram-generator.settings = lib.listToAttrs (
(builtins.map builtins.map (dev: {
(dev: { name = dev;
name = dev; value =
value = let
let size = "${toString cfg.memoryPercent} / 100 * ram";
size = "${toString cfg.memoryPercent} / 100 * ram"; in
in {
{ zram-size =
zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size; if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
compression-algorithm = cfg.algorithm; compression-algorithm = cfg.algorithm;
swap-priority = cfg.priority; swap-priority = cfg.priority;
} // lib.optionalAttrs (cfg.writebackDevice != null) { }
writeback-device = cfg.writebackDevice; // lib.optionalAttrs (cfg.writebackDevice != null) {
}; writeback-device = cfg.writebackDevice;
}) };
devices); }) devices
);
}; };

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.hardware.deviceTree; cfg = config.hardware.deviceTree;
@ -62,10 +67,12 @@ let
}; };
}; };
filterDTBs = src: if cfg.filter == null filterDTBs =
then src src:
if cfg.filter == null then
src
else else
pkgs.runCommand "dtbs-filtered" {} '' pkgs.runCommand "dtbs-filtered" { } ''
mkdir -p $out mkdir -p $out
cd ${src} cd ${src}
find . -type f -name '${cfg.filter}' -print0 \ find . -type f -name '${cfg.filter}' -print0 \
@ -76,148 +83,169 @@ let
# Fill in `dtboFile` for each overlay if not set already. # Fill in `dtboFile` for each overlay if not set already.
# Existence of one of these is guarded by assertion below # Existence of one of these is guarded by assertion below
withDTBOs = xs: lib.flip map xs (o: o // { dtboFile = withDTBOs =
let xs:
includePaths = ["${lib.getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes"] ++ cfg.dtboBuildExtraIncludePaths; lib.flip map xs (
extraPreprocessorFlags = cfg.dtboBuildExtraPreprocessorFlags; o:
in o
if o.dtboFile == null then // {
let dtboFile =
dtsFile = if o.dtsFile == null then (pkgs.writeText "dts" o.dtsText) else o.dtsFile; let
in includePaths = [
pkgs.deviceTree.compileDTS { "${lib.getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes"
name = "${o.name}-dtbo"; ] ++ cfg.dtboBuildExtraIncludePaths;
inherit includePaths extraPreprocessorFlags dtsFile; extraPreprocessorFlags = cfg.dtboBuildExtraPreprocessorFlags;
in
if o.dtboFile == null then
let
dtsFile = if o.dtsFile == null then (pkgs.writeText "dts" o.dtsText) else o.dtsFile;
in
pkgs.deviceTree.compileDTS {
name = "${o.name}-dtbo";
inherit includePaths extraPreprocessorFlags dtsFile;
}
else
o.dtboFile;
} }
else o.dtboFile; } ); );
in in
{ {
imports = [ imports = [
(lib.mkRemovedOptionModule [ "hardware" "deviceTree" "base" ] "Use hardware.deviceTree.kernelPackage instead") (lib.mkRemovedOptionModule [
"hardware"
"deviceTree"
"base"
] "Use hardware.deviceTree.kernelPackage instead")
]; ];
options = { options = {
hardware.deviceTree = { hardware.deviceTree = {
enable = lib.mkOption { enable = lib.mkOption {
default = pkgs.stdenv.hostPlatform.linux-kernel.DTB or false; default = pkgs.stdenv.hostPlatform.linux-kernel.DTB or false;
type = lib.types.bool; type = lib.types.bool;
description = '' description = ''
Build device tree files. These are used to describe the Build device tree files. These are used to describe the
non-discoverable hardware of a system. non-discoverable hardware of a system.
''; '';
}; };
kernelPackage = lib.mkOption { kernelPackage = lib.mkOption {
default = config.boot.kernelPackages.kernel; default = config.boot.kernelPackages.kernel;
defaultText = lib.literalExpression "config.boot.kernelPackages.kernel"; defaultText = lib.literalExpression "config.boot.kernelPackages.kernel";
example = lib.literalExpression "pkgs.linux_latest"; example = lib.literalExpression "pkgs.linux_latest";
type = lib.types.path; type = lib.types.path;
description = '' description = ''
Kernel package where device tree include directory is from. Also used as default source of dtb package to apply overlays to Kernel package where device tree include directory is from. Also used as default source of dtb package to apply overlays to
''; '';
}; };
dtboBuildExtraPreprocessorFlags = lib.mkOption { dtboBuildExtraPreprocessorFlags = lib.mkOption {
default = []; default = [ ];
example = lib.literalExpression "[ \"-DMY_DTB_DEFINE\" ]"; example = lib.literalExpression "[ \"-DMY_DTB_DEFINE\" ]";
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
description = '' description = ''
Additional flags to pass to the preprocessor during dtbo compilations Additional flags to pass to the preprocessor during dtbo compilations
''; '';
}; };
dtboBuildExtraIncludePaths = lib.mkOption { dtboBuildExtraIncludePaths = lib.mkOption {
default = []; default = [ ];
example = lib.literalExpression '' example = lib.literalExpression ''
[ [
./my_custom_include_dir_1 ./my_custom_include_dir_1
./custom_include_dir_2 ./custom_include_dir_2
] ]
''; '';
type = lib.types.listOf lib.types.path; type = lib.types.listOf lib.types.path;
description = '' description = ''
Additional include paths that will be passed to the preprocessor when creating the final .dts to compile into .dtbo Additional include paths that will be passed to the preprocessor when creating the final .dts to compile into .dtbo
''; '';
}; };
dtbSource = lib.mkOption { dtbSource = lib.mkOption {
default = "${cfg.kernelPackage}/dtbs"; default = "${cfg.kernelPackage}/dtbs";
defaultText = lib.literalExpression "\${cfg.kernelPackage}/dtbs"; defaultText = lib.literalExpression "\${cfg.kernelPackage}/dtbs";
type = lib.types.path; type = lib.types.path;
description = '' description = ''
Path to dtb directory that overlays and other processing will be applied to. Uses Path to dtb directory that overlays and other processing will be applied to. Uses
device trees bundled with the Linux kernel by default. device trees bundled with the Linux kernel by default.
''; '';
}; };
name = lib.mkOption { name = lib.mkOption {
default = null; default = null;
example = "some-dtb.dtb"; example = "some-dtb.dtb";
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
description = '' description = ''
The name of an explicit dtb to be loaded, relative to the dtb base. The name of an explicit dtb to be loaded, relative to the dtb base.
Useful in extlinux scenarios if the bootloader doesn't pick the Useful in extlinux scenarios if the bootloader doesn't pick the
right .dtb file from FDTDIR. right .dtb file from FDTDIR.
''; '';
}; };
filter = lib.mkOption { filter = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
example = "*rpi*.dtb"; example = "*rpi*.dtb";
description = '' description = ''
Only include .dtb files matching glob expression. Only include .dtb files matching glob expression.
''; '';
}; };
overlays = lib.mkOption { overlays = lib.mkOption {
default = []; default = [ ];
example = lib.literalExpression '' example = lib.literalExpression ''
[ [
{ name = "pps"; dtsFile = ./dts/pps.dts; } { name = "pps"; dtsFile = ./dts/pps.dts; }
{ name = "spi"; { name = "spi";
dtsText = "..."; dtsText = "...";
} }
{ name = "precompiled"; dtboFile = ./dtbos/example.dtbo; } { name = "precompiled"; dtboFile = ./dtbos/example.dtbo; }
] ]
''; '';
type = lib.types.listOf (lib.types.coercedTo lib.types.path (path: { type = lib.types.listOf (
lib.types.coercedTo lib.types.path (path: {
name = baseNameOf path; name = baseNameOf path;
filter = null; filter = null;
dtboFile = path; dtboFile = path;
}) overlayType); }) overlayType
description = '' );
List of overlays to apply to base device-tree (.dtb) files. description = ''
''; List of overlays to apply to base device-tree (.dtb) files.
}; '';
package = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.path;
internal = true;
description = ''
A path containing the result of applying `overlays` to `kernelPackage`.
'';
};
}; };
package = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.path;
internal = true;
description = ''
A path containing the result of applying `overlays` to `kernelPackage`.
'';
};
};
}; };
config = lib.mkIf (cfg.enable) { config = lib.mkIf (cfg.enable) {
assertions = let assertions =
invalidOverlay = o: (o.dtsFile == null) && (o.dtsText == null) && (o.dtboFile == null); let
in lib.singleton { invalidOverlay = o: (o.dtsFile == null) && (o.dtsText == null) && (o.dtboFile == null);
assertion = lib.all (o: !invalidOverlay o) cfg.overlays; in
message = '' lib.singleton {
deviceTree overlay needs one of dtsFile, dtsText or dtboFile set. assertion = lib.all (o: !invalidOverlay o) cfg.overlays;
Offending overlay(s): message = ''
${toString (map (o: o.name) (builtins.filter invalidOverlay cfg.overlays))} deviceTree overlay needs one of dtsFile, dtsText or dtboFile set.
''; Offending overlay(s):
}; ${toString (map (o: o.name) (builtins.filter invalidOverlay cfg.overlays))}
'';
};
hardware.deviceTree.package = if (cfg.overlays != []) hardware.deviceTree.package =
then pkgs.deviceTree.applyOverlays filteredDTBs (withDTBOs cfg.overlays) if (cfg.overlays != [ ]) then
else filteredDTBs; pkgs.deviceTree.applyOverlays filteredDTBs (withDTBOs cfg.overlays)
else
filteredDTBs;
}; };
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.hardware.graphics; cfg = config.hardware.graphics;
@ -14,16 +19,35 @@ let
in in
{ {
imports = [ imports = [
(lib.mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "graphics" "extraPackages" ]) (lib.mkRenamedOptionModule
(lib.mkRemovedOptionModule [ "hardware" "opengl" "s3tcSupport" ] "S3TC support is now always enabled in Mesa.") [ "services" "xserver" "vaapiDrivers" ]
(lib.mkRemovedOptionModule [ "hardware" "opengl" "driSupport"] "The setting can be removed.") [ "hardware" "graphics" "extraPackages" ]
)
(lib.mkRemovedOptionModule [
"hardware"
"opengl"
"s3tcSupport"
] "S3TC support is now always enabled in Mesa.")
(lib.mkRemovedOptionModule [ "hardware" "opengl" "driSupport" ] "The setting can be removed.")
(lib.mkRenamedOptionModule [ "hardware" "opengl" "enable"] [ "hardware" "graphics" "enable" ]) (lib.mkRenamedOptionModule [ "hardware" "opengl" "enable" ] [ "hardware" "graphics" "enable" ])
(lib.mkRenamedOptionModule [ "hardware" "opengl" "driSupport32Bit"] [ "hardware" "graphics" "enable32Bit" ]) (lib.mkRenamedOptionModule
(lib.mkRenamedOptionModule [ "hardware" "opengl" "package"] [ "hardware" "graphics" "package" ]) [ "hardware" "opengl" "driSupport32Bit" ]
(lib.mkRenamedOptionModule [ "hardware" "opengl" "package32"] [ "hardware" "graphics" "package32" ]) [ "hardware" "graphics" "enable32Bit" ]
(lib.mkRenamedOptionModule [ "hardware" "opengl" "extraPackages"] [ "hardware" "graphics" "extraPackages" ]) )
(lib.mkRenamedOptionModule [ "hardware" "opengl" "extraPackages32"] [ "hardware" "graphics" "extraPackages32" ]) (lib.mkRenamedOptionModule [ "hardware" "opengl" "package" ] [ "hardware" "graphics" "package" ])
(lib.mkRenamedOptionModule
[ "hardware" "opengl" "package32" ]
[ "hardware" "graphics" "package32" ]
)
(lib.mkRenamedOptionModule
[ "hardware" "opengl" "extraPackages" ]
[ "hardware" "graphics" "extraPackages" ]
)
(lib.mkRenamedOptionModule
[ "hardware" "opengl" "extraPackages32" ]
[ "hardware" "graphics" "extraPackages32" ]
)
]; ];
options.hardware.graphics = { options.hardware.graphics = {
@ -78,7 +102,7 @@ in
::: :::
''; '';
type = lib.types.listOf lib.types.package; type = lib.types.listOf lib.types.package;
default = []; default = [ ];
example = lib.literalExpression "with pkgs; [ intel-media-driver intel-ocl intel-vaapi-driver ]"; example = lib.literalExpression "with pkgs; [ intel-media-driver intel-ocl intel-vaapi-driver ]";
}; };
@ -92,7 +116,7 @@ in
::: :::
''; '';
type = lib.types.listOf lib.types.package; type = lib.types.listOf lib.types.package;
default = []; default = [ ];
example = lib.literalExpression "with pkgs.pkgsi686Linux; [ intel-media-driver intel-vaapi-driver ]"; example = lib.literalExpression "with pkgs.pkgsi686Linux; [ intel-media-driver intel-vaapi-driver ]";
}; };
}; };
@ -117,7 +141,7 @@ in
else if cfg.enable32Bit then else if cfg.enable32Bit then
{ "L+".argument = toString driversEnv32; } { "L+".argument = toString driversEnv32; }
else else
{ "r" = {}; }; { "r" = { }; };
}; };
hardware.graphics.package = lib.mkDefault pkgs.mesa.drivers; hardware.graphics.package = lib.mkDefault pkgs.mesa.drivers;

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.hardware.steam-hardware; cfg = config.hardware.steam-hardware;

View file

@ -92,21 +92,23 @@ in
information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux
''; '';
modesetting.enable = lib.mkEnableOption '' modesetting.enable =
kernel modesetting when using the NVIDIA proprietary driver. lib.mkEnableOption ''
kernel modesetting when using the NVIDIA proprietary driver.
Enabling this fixes screen tearing when using Optimus via PRIME (see Enabling this fixes screen tearing when using Optimus via PRIME (see
{option}`hardware.nvidia.prime.sync.enable`. This is not enabled {option}`hardware.nvidia.prime.sync.enable`. This is not enabled
by default because it is not officially supported by NVIDIA and would not by default because it is not officially supported by NVIDIA and would not
work with SLI. work with SLI.
Enabling this and using version 545 or newer of the proprietary NVIDIA Enabling this and using version 545 or newer of the proprietary NVIDIA
driver causes it to provide its own framebuffer device, which can cause driver causes it to provide its own framebuffer device, which can cause
Wayland compositors to work when they otherwise wouldn't. Wayland compositors to work when they otherwise wouldn't.
'' // { ''
default = lib.versionAtLeast cfg.package.version "535"; // {
defaultText = lib.literalExpression "lib.versionAtLeast cfg.package.version \"535\""; default = lib.versionAtLeast cfg.package.version "535";
}; defaultText = lib.literalExpression "lib.versionAtLeast cfg.package.version \"535\"";
};
prime.nvidiaBusId = lib.mkOption { prime.nvidiaBusId = lib.mkOption {
type = busIDType; type = busIDType;
@ -266,14 +268,16 @@ in
''; '';
}; };
gsp.enable = lib.mkEnableOption '' gsp.enable =
the GPU System Processor (GSP) on the video card lib.mkEnableOption ''
'' // { the GPU System Processor (GSP) on the video card
default = useOpenModules || lib.versionAtLeast nvidia_x11.version "555"; ''
defaultText = lib.literalExpression '' // {
config.hardware.nvidia.open == true || lib.versionAtLeast config.hardware.nvidia.package.version "555" default = useOpenModules || lib.versionAtLeast nvidia_x11.version "555";
''; defaultText = lib.literalExpression ''
}; config.hardware.nvidia.open == true || lib.versionAtLeast config.hardware.nvidia.package.version "555"
'';
};
}; };
}; };
@ -315,7 +319,9 @@ in
softdep nvidia post: nvidia-uvm softdep nvidia post: nvidia-uvm
''; '';
}; };
systemd.tmpfiles.rules = lib.mkIf config.virtualisation.docker.enableNvidia [ "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin" ]; systemd.tmpfiles.rules = lib.mkIf config.virtualisation.docker.enableNvidia [
"L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin"
];
services.udev.extraRules = '' services.udev.extraRules = ''
# Create /dev/nvidia-uvm when the nvidia-uvm module is loaded. # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c 195 255'" KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c 195 255'"
@ -616,7 +622,9 @@ in
# If requested enable modesetting via kernel parameters. # If requested enable modesetting via kernel parameters.
kernelParams = kernelParams =
lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1" lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
++ lib.optional ((offloadCfg.enable || cfg.modesetting.enable) && lib.versionAtLeast nvidia_x11.version "545") "nvidia-drm.fbdev=1" ++ lib.optional (
(offloadCfg.enable || cfg.modesetting.enable) && lib.versionAtLeast nvidia_x11.version "545"
) "nvidia-drm.fbdev=1"
++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1" ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
++ lib.optional useOpenModules "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1" ++ lib.optional useOpenModules "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off"; ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
@ -677,7 +685,9 @@ in
TOPOLOGY_FILE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch"; TOPOLOGY_FILE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
DATABASE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch"; DATABASE_PATH = "${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
}; };
nv-fab-conf = settingsFormat.generate "fabricmanager.conf" (fabricManagerConfDefaults // cfg.datacenter.settings); nv-fab-conf = settingsFormat.generate "fabricmanager.conf" (
fabricManagerConfDefaults // cfg.datacenter.settings
);
in in
"${lib.getExe nvidia_x11.fabricmanager} -c ${nv-fab-conf}"; "${lib.getExe nvidia_x11.fabricmanager} -c ${nv-fab-conf}";
LimitCORE = "infinity"; LimitCORE = "infinity";

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
options.hardware.wooting.enable = lib.mkEnableOption '' options.hardware.wooting.enable = lib.mkEnableOption ''
support for Wooting keyboards. support for Wooting keyboards.

View file

@ -1,11 +1,17 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
imcfg = config.i18n.inputMethod; imcfg = config.i18n.inputMethod;
cfg = imcfg.fcitx5; cfg = imcfg.fcitx5;
fcitx5Package = fcitx5Package =
if cfg.plasma6Support if cfg.plasma6Support then
then pkgs.qt6Packages.fcitx5-with-addons.override { inherit (cfg) addons; } pkgs.qt6Packages.fcitx5-with-addons.override { inherit (cfg) addons; }
else pkgs.libsForQt5.fcitx5-with-addons.override { inherit (cfg) addons; }; else
pkgs.libsForQt5.fcitx5-with-addons.override { inherit (cfg) addons; };
settingsFormat = pkgs.formats.ini { }; settingsFormat = pkgs.formats.ini { };
in in
{ {
@ -108,40 +114,46 @@ in
config = lib.mkIf (imcfg.enable && imcfg.type == "fcitx5") { config = lib.mkIf (imcfg.enable && imcfg.type == "fcitx5") {
i18n.inputMethod.package = fcitx5Package; i18n.inputMethod.package = fcitx5Package;
i18n.inputMethod.fcitx5.addons = lib.optionals (cfg.quickPhrase != { }) [ i18n.inputMethod.fcitx5.addons =
(pkgs.writeTextDir "share/fcitx5/data/QuickPhrase.mb" lib.optionals (cfg.quickPhrase != { }) [
(lib.concatStringsSep "\n" (pkgs.writeTextDir "share/fcitx5/data/QuickPhrase.mb" (
(lib.mapAttrsToList (name: value: "${name} ${value}") cfg.quickPhrase))) lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") cfg.quickPhrase)
] ++ lib.optionals (cfg.quickPhraseFiles != { }) [ ))
(pkgs.linkFarm "quickPhraseFiles" (lib.mapAttrs' ]
(name: value: lib.nameValuePair ("share/fcitx5/data/quickphrase.d/${name}.mb") value) ++ lib.optionals (cfg.quickPhraseFiles != { }) [
cfg.quickPhraseFiles)) (pkgs.linkFarm "quickPhraseFiles" (
]; lib.mapAttrs' (
name: value: lib.nameValuePair ("share/fcitx5/data/quickphrase.d/${name}.mb") value
) cfg.quickPhraseFiles
))
];
environment.etc = environment.etc =
let let
optionalFile = p: f: v: lib.optionalAttrs (v != { }) { optionalFile =
"xdg/fcitx5/${p}".text = f v; p: f: v:
}; lib.optionalAttrs (v != { }) {
"xdg/fcitx5/${p}".text = f v;
};
in in
lib.attrsets.mergeAttrsList [ lib.attrsets.mergeAttrsList [
(optionalFile "config" (lib.generators.toINI { }) cfg.settings.globalOptions) (optionalFile "config" (lib.generators.toINI { }) cfg.settings.globalOptions)
(optionalFile "profile" (lib.generators.toINI { }) cfg.settings.inputMethod) (optionalFile "profile" (lib.generators.toINI { }) cfg.settings.inputMethod)
(lib.concatMapAttrs (lib.concatMapAttrs (
(name: value: optionalFile name: value: optionalFile "conf/${name}.conf" (lib.generators.toINIWithGlobalSection { }) value
"conf/${name}.conf" ) cfg.settings.addons)
(lib.generators.toINIWithGlobalSection { })
value)
cfg.settings.addons)
]; ];
environment.variables = { environment.variables =
XMODIFIERS = "@im=fcitx"; {
QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ]; XMODIFIERS = "@im=fcitx";
} // lib.optionalAttrs (!cfg.waylandFrontend) { QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
GTK_IM_MODULE = "fcitx"; }
QT_IM_MODULE = "fcitx"; // lib.optionalAttrs (!cfg.waylandFrontend) {
} // lib.optionalAttrs cfg.ignoreUserConfig { GTK_IM_MODULE = "fcitx";
SKIP_FCITX_USER_PATH = "1"; QT_IM_MODULE = "fcitx";
}; }
// lib.optionalAttrs cfg.ignoreUserConfig {
SKIP_FCITX_USER_PATH = "1";
};
}; };
} }

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
with lib; with lib;
@ -7,9 +12,9 @@ let
cfg = imcfg.ibus; cfg = imcfg.ibus;
ibusPackage = pkgs.ibus-with-plugins.override { plugins = cfg.engines; }; ibusPackage = pkgs.ibus-with-plugins.override { plugins = cfg.engines; };
ibusEngine = lib.types.mkOptionType { ibusEngine = lib.types.mkOptionType {
name = "ibus-engine"; name = "ibus-engine";
inherit (lib.types.package) descriptionClass merge; inherit (lib.types.package) descriptionClass merge;
check = x: (lib.types.package.check x) && (attrByPath ["meta" "isIbusEngine"] false x); check = x: (lib.types.package.check x) && (attrByPath [ "meta" "isIbusEngine" ] false x);
}; };
impanel = optionalString (cfg.panel != null) "--panel=${cfg.panel}"; impanel = optionalString (cfg.panel != null) "--panel=${cfg.panel}";
@ -35,15 +40,15 @@ in
options = { options = {
i18n.inputMethod.ibus = { i18n.inputMethod.ibus = {
engines = mkOption { engines = mkOption {
type = with types; listOf ibusEngine; type = with types; listOf ibusEngine;
default = []; default = [ ];
example = literalExpression "with pkgs.ibus-engines; [ mozc hangul ]"; example = literalExpression "with pkgs.ibus-engines; [ mozc hangul ]";
description = description =
let let
enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines; enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines;
engines = concatStringsSep ", " engines = concatStringsSep ", " (map (name: "`${name}`") (attrNames enginesDrv));
(map (name: "`${name}`") (attrNames enginesDrv)); in
in "Enabled IBus engines. Available engines are: ${engines}."; "Enabled IBus engines. Available engines are: ${engines}.";
}; };
panel = mkOption { panel = mkOption {
type = with types; nullOr path; type = with types; nullOr path;

View file

@ -1,80 +1,106 @@
# This is an expression meant to be called from `./repart.nix`, it is NOT a # This is an expression meant to be called from `./repart.nix`, it is NOT a
# NixOS module that can be imported. # NixOS module that can be imported.
{ lib {
, stdenvNoCC lib,
, runCommand stdenvNoCC,
, python3 runCommand,
, black python3,
, ruff black,
, mypy ruff,
, systemd mypy,
, fakeroot systemd,
fakeroot,
# filesystem tools # filesystem tools
, dosfstools dosfstools,
, mtools mtools,
, e2fsprogs e2fsprogs,
, squashfsTools squashfsTools,
, erofs-utils erofs-utils,
, btrfs-progs btrfs-progs,
, xfsprogs xfsprogs,
# compression tools # compression tools
, zstd zstd,
, xz xz,
# arguments # arguments
, name name,
, version version,
, imageFileBasename imageFileBasename,
, compression compression,
, fileSystems fileSystems,
, finalPartitions finalPartitions,
, split split,
, seed seed,
, definitionsDirectory definitionsDirectory,
, sectorSize sectorSize,
, mkfsEnv ? {} mkfsEnv ? { },
, createEmpty ? true createEmpty ? true,
}: }:
let let
systemdArch = let systemdArch =
inherit (stdenvNoCC) hostPlatform; let
in inherit (stdenvNoCC) hostPlatform;
if hostPlatform.isAarch32 then "arm" in
else if hostPlatform.isAarch64 then "arm64" if hostPlatform.isAarch32 then
else if hostPlatform.isx86_32 then "x86" "arm"
else if hostPlatform.isx86_64 then "x86-64" else if hostPlatform.isAarch64 then
else if hostPlatform.isMips32 then "mips-le" "arm64"
else if hostPlatform.isMips64 then "mips64-le" else if hostPlatform.isx86_32 then
else if hostPlatform.isPower then "ppc" "x86"
else if hostPlatform.isPower64 then "ppc64" else if hostPlatform.isx86_64 then
else if hostPlatform.isRiscV32 then "riscv32" "x86-64"
else if hostPlatform.isRiscV64 then "riscv64" else if hostPlatform.isMips32 then
else if hostPlatform.isS390 then "s390" "mips-le"
else if hostPlatform.isS390x then "s390x" else if hostPlatform.isMips64 then
else if hostPlatform.isLoongArch64 then "loongarch64" "mips64-le"
else if hostPlatform.isAlpha then "alpha" else if hostPlatform.isPower then
else hostPlatform.parsed.cpu.name; "ppc"
else if hostPlatform.isPower64 then
"ppc64"
else if hostPlatform.isRiscV32 then
"riscv32"
else if hostPlatform.isRiscV64 then
"riscv64"
else if hostPlatform.isS390 then
"s390"
else if hostPlatform.isS390x then
"s390x"
else if hostPlatform.isLoongArch64 then
"loongarch64"
else if hostPlatform.isAlpha then
"alpha"
else
hostPlatform.parsed.cpu.name;
amendRepartDefinitions = runCommand "amend-repart-definitions.py" amendRepartDefinitions =
{ runCommand "amend-repart-definitions.py"
# TODO: ruff does not splice properly in nativeBuildInputs {
depsBuildBuild = [ ruff ]; # TODO: ruff does not splice properly in nativeBuildInputs
nativeBuildInputs = [ python3 black mypy ]; depsBuildBuild = [ ruff ];
} '' nativeBuildInputs = [
install ${./amend-repart-definitions.py} $out python3
patchShebangs --build $out black
mypy
];
}
''
install ${./amend-repart-definitions.py} $out
patchShebangs --build $out
black --check --diff $out black --check --diff $out
ruff check --line-length 88 $out ruff check --line-length 88 $out
mypy --strict $out mypy --strict $out
''; '';
fileSystemToolMapping = { fileSystemToolMapping = {
"vfat" = [ dosfstools mtools ]; "vfat" = [
dosfstools
mtools
];
"ext4" = [ e2fsprogs.bin ]; "ext4" = [ e2fsprogs.bin ];
"squashfs" = [ squashfsTools ]; "squashfs" = [ squashfsTools ];
"erofs" = [ erofs-utils ]; "erofs" = [ erofs-utils ];
@ -84,105 +110,123 @@ let
fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems; fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
compressionPkg = { compressionPkg =
"zstd" = zstd; {
"xz" = xz; "zstd" = zstd;
}."${compression.algorithm}"; "xz" = xz;
}
."${compression.algorithm}";
compressionCommand = { compressionCommand =
"zstd" = "zstd --no-progress --threads=$NIX_BUILD_CORES -${toString compression.level}"; {
"xz" = "xz --keep --verbose --threads=$NIX_BUILD_CORES -${toString compression.level}"; "zstd" = "zstd --no-progress --threads=$NIX_BUILD_CORES -${toString compression.level}";
}."${compression.algorithm}"; "xz" = "xz --keep --verbose --threads=$NIX_BUILD_CORES -${toString compression.level}";
}
."${compression.algorithm}";
in in
stdenvNoCC.mkDerivation (finalAttrs: stdenvNoCC.mkDerivation (
(if (version != null) finalAttrs:
then { pname = name; inherit version; } (
else { inherit name; } if (version != null) then
) // { {
__structuredAttrs = true; pname = name;
inherit version;
}
else
{ inherit name; }
)
// {
__structuredAttrs = true;
# the image will be self-contained so we can drop references
# to the closure that was used to build it
unsafeDiscardReferences.out = true;
# the image will be self-contained so we can drop references nativeBuildInputs =
# to the closure that was used to build it [
unsafeDiscardReferences.out = true; systemd
fakeroot
]
++ lib.optionals (compression.enable) [
compressionPkg
]
++ fileSystemTools;
nativeBuildInputs = [ env = mkfsEnv;
systemd
fakeroot
] ++ lib.optionals (compression.enable) [
compressionPkg
] ++ fileSystemTools;
env = mkfsEnv; inherit finalPartitions definitionsDirectory;
inherit finalPartitions definitionsDirectory; partitionsJSON = builtins.toJSON finalAttrs.finalPartitions;
partitionsJSON = builtins.toJSON finalAttrs.finalPartitions; # relative path to the repart definitions that are read by systemd-repart
finalRepartDefinitions = "repart.d";
# relative path to the repart definitions that are read by systemd-repart systemdRepartFlags =
finalRepartDefinitions = "repart.d"; [
"--architecture=${systemdArch}"
"--dry-run=no"
"--size=auto"
"--seed=${seed}"
"--definitions=${finalAttrs.finalRepartDefinitions}"
"--split=${lib.boolToString split}"
"--json=pretty"
]
++ lib.optionals createEmpty [
"--empty=create"
]
++ lib.optionals (sectorSize != null) [
"--sector-size=${toString sectorSize}"
];
systemdRepartFlags = [ dontUnpack = true;
"--architecture=${systemdArch}" dontConfigure = true;
"--dry-run=no" doCheck = false;
"--size=auto"
"--seed=${seed}"
"--definitions=${finalAttrs.finalRepartDefinitions}"
"--split=${lib.boolToString split}"
"--json=pretty"
] ++ lib.optionals createEmpty [
"--empty=create"
] ++ lib.optionals (sectorSize != null) [
"--sector-size=${toString sectorSize}"
];
dontUnpack = true; patchPhase = ''
dontConfigure = true; runHook prePatch
doCheck = false;
patchPhase = '' amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory)
runHook prePatch ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory) runHook postPatch
ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions '';
runHook postPatch buildPhase = ''
''; runHook preBuild
buildPhase = '' echo "Building image with systemd-repart..."
runHook preBuild fakeroot systemd-repart \
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
echo "Building image with systemd-repart..." runHook postBuild
fakeroot systemd-repart \ '';
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
runHook postBuild installPhase =
''; ''
runHook preInstall
installPhase = '' mkdir -p $out
runHook preInstall ''
# Compression is implemented in the same derivation as opposed to in a
# separate derivation to allow users to save disk space. Disk images are
# already very space intensive so we want to allow users to mitigate this.
+ lib.optionalString compression.enable ''
for f in ${imageFileBasename}*; do
echo "Compressing $f with ${compression.algorithm}..."
# Keep the original file when compressing and only delete it afterwards
${compressionCommand} $f && rm $f
done
''
+ ''
mv -v repart-output.json ${imageFileBasename}* $out
mkdir -p $out runHook postInstall
'' '';
# Compression is implemented in the same derivation as opposed to in a
# separate derivation to allow users to save disk space. Disk images are
# already very space intensive so we want to allow users to mitigate this.
+ lib.optionalString compression.enable
''
for f in ${imageFileBasename}*; do
echo "Compressing $f with ${compression.algorithm}..."
# Keep the original file when compressing and only delete it afterwards
${compressionCommand} $f && rm $f
done
'' + ''
mv -v repart-output.json ${imageFileBasename}* $out
runHook postInstall passthru = {
''; inherit amendRepartDefinitions;
};
passthru = { }
inherit amendRepartDefinitions; )
};
})

View file

@ -1,7 +1,13 @@
# This module exposes options to build a disk image with a GUID Partition Table # This module exposes options to build a disk image with a GUID Partition Table
# (GPT). It uses systemd-repart to build the image. # (GPT). It uses systemd-repart to build the image.
{ config, pkgs, lib, utils, ... }: {
config,
pkgs,
lib,
utils,
...
}:
let let
cfg = config.image.repart; cfg = config.image.repart;
@ -27,14 +33,16 @@ let
}; };
contents = lib.mkOption { contents = lib.mkOption {
type = with lib.types; attrsOf (submodule { type =
options = { with lib.types;
source = lib.mkOption { attrsOf (submodule {
type = types.path; options = {
description = "Path of the source file."; source = lib.mkOption {
type = types.path;
description = "Path of the source file.";
};
}; };
}; });
});
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
@ -48,7 +56,13 @@ let
}; };
repartConfig = lib.mkOption { repartConfig = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ str int bool ]); type =
with lib.types;
attrsOf (oneOf [
str
int
bool
]);
example = { example = {
Type = "home"; Type = "home";
SizeMinBytes = "512M"; SizeMinBytes = "512M";
@ -63,10 +77,12 @@ let
}; };
}; };
mkfsOptionsToEnv = opts: lib.mapAttrs' (fsType: options: { mkfsOptionsToEnv =
name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}"; opts:
value = builtins.concatStringsSep " " options; lib.mapAttrs' (fsType: options: {
}) opts; name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
value = builtins.concatStringsSep " " options;
}) opts;
in in
{ {
imports = [ imports = [
@ -113,7 +129,10 @@ in
enable = lib.mkEnableOption "Image compression"; enable = lib.mkEnableOption "Image compression";
algorithm = lib.mkOption { algorithm = lib.mkOption {
type = lib.types.enum [ "zstd" "xz" ]; type = lib.types.enum [
"zstd"
"xz"
];
default = "zstd"; default = "zstd";
description = "Compression algorithm"; description = "Compression algorithm";
}; };
@ -159,7 +178,10 @@ in
package = lib.mkPackageOption pkgs "systemd-repart" { package = lib.mkPackageOption pkgs "systemd-repart" {
# We use buildPackages so that repart images are built with the build # We use buildPackages so that repart images are built with the build
# platform's systemd, allowing for cross-compiled systems to work. # platform's systemd, allowing for cross-compiled systems to work.
default = [ "buildPackages" "systemd" ]; default = [
"buildPackages"
"systemd"
];
example = "pkgs.buildPackages.systemdMinimal.override { withCryptsetup = true; }"; example = "pkgs.buildPackages.systemdMinimal.override { withCryptsetup = true; }";
}; };
@ -196,7 +218,7 @@ in
mkfsOptions = lib.mkOption { mkfsOptions = lib.mkOption {
type = with lib.types; attrsOf (listOf str); type = with lib.types; attrsOf (listOf str);
default = {}; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
vfat = [ "-S 512" "-c" ]; vfat = [ "-S 512" "-c" ];
@ -230,7 +252,8 @@ in
config = { config = {
assertions = lib.mapAttrsToList (fileName: partitionConfig: assertions = lib.mapAttrsToList (
fileName: partitionConfig:
let let
inherit (partitionConfig) repartConfig; inherit (partitionConfig) repartConfig;
labelLength = builtins.stringLength repartConfig.Label; labelLength = builtins.stringLength repartConfig.Label;
@ -240,51 +263,58 @@ in
message = '' message = ''
The partition label '${repartConfig.Label}' The partition label '${repartConfig.Label}'
defined for '${fileName}' is ${toString labelLength} characters long, defined for '${fileName}' is ${toString labelLength} characters long,
but the maximum label length supported by UEFI is ${toString but the maximum label length supported by UEFI is ${toString GPTMaxLabelLength}.
GPTMaxLabelLength}.
''; '';
} }
) cfg.partitions; ) cfg.partitions;
warnings = lib.filter (v: v != null) (lib.mapAttrsToList (fileName: partitionConfig: warnings = lib.filter (v: v != null) (
let lib.mapAttrsToList (
inherit (partitionConfig) repartConfig; fileName: partitionConfig:
suggestedMaxLabelLength = GPTMaxLabelLength - 2; let
labelLength = builtins.stringLength repartConfig.Label; inherit (partitionConfig) repartConfig;
in suggestedMaxLabelLength = GPTMaxLabelLength - 2;
if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then '' labelLength = builtins.stringLength repartConfig.Label;
The partition label '${repartConfig.Label}' in
defined for '${fileName}' is ${toString labelLength} characters long. if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
The suggested maximum label length is ${toString ''
suggestedMaxLabelLength}. The partition label '${repartConfig.Label}'
defined for '${fileName}' is ${toString labelLength} characters long.
The suggested maximum label length is ${toString suggestedMaxLabelLength}.
If you use sytemd-sysupdate style A/B updates, this might If you use sytemd-sysupdate style A/B updates, this might
not leave enough space to increment the version number included in not leave enough space to increment the version number included in
the label in a future release. For example, if your label is the label in a future release. For example, if your label is
${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
you're at version 9, you cannot increment this to 10. you're at version 9, you cannot increment this to 10.
'' else null ''
) cfg.partitions); else
null
) cfg.partitions
);
image.repart = image.repart =
let let
version = config.image.repart.version; version = config.image.repart.version;
versionInfix = if version != null then "_${version}" else ""; versionInfix = if version != null then "_${version}" else "";
compressionSuffix = lib.optionalString cfg.compression.enable compressionSuffix =
{ lib.optionalString cfg.compression.enable
"zstd" = ".zst"; {
"xz" = ".xz"; "zstd" = ".zst";
}."${cfg.compression.algorithm}"; "xz" = ".xz";
}
."${cfg.compression.algorithm}";
makeClosure = paths: pkgs.closureInfo { rootPaths = paths; }; makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
# Add the closure of the provided Nix store paths to cfg.partitions so # Add the closure of the provided Nix store paths to cfg.partitions so
# that amend-repart-definitions.py can read it. # that amend-repart-definitions.py can read it.
addClosure = _name: partitionConfig: partitionConfig // ( addClosure =
lib.optionalAttrs _name: partitionConfig:
(partitionConfig.storePaths or [ ] != [ ]) partitionConfig
{ closure = "${makeClosure partitionConfig.storePaths}/store-paths"; } // (lib.optionalAttrs (partitionConfig.storePaths or [ ] != [ ]) {
); closure = "${makeClosure partitionConfig.storePaths}/store-paths";
});
in in
{ {
name = lib.mkIf (config.system.image.id != null) (lib.mkOptionDefault config.system.image.id); name = lib.mkIf (config.system.image.id != null) (lib.mkOptionDefault config.system.image.id);
@ -295,10 +325,13 @@ in
# Generally default to slightly faster than default compression # Generally default to slightly faster than default compression
# levels under the assumption that most of the building will be done # levels under the assumption that most of the building will be done
# for development and release builds will be customized. # for development and release builds will be customized.
level = lib.mkOptionDefault { level =
"zstd" = 3; lib.mkOptionDefault
"xz" = 3; {
}."${cfg.compression.algorithm}"; "zstd" = 3;
"xz" = 3;
}
."${cfg.compression.algorithm}";
}; };
finalPartitions = lib.mapAttrs addClosure cfg.partitions; finalPartitions = lib.mapAttrs addClosure cfg.partitions;
@ -306,27 +339,37 @@ in
system.build.image = system.build.image =
let let
fileSystems = lib.filter fileSystems = lib.filter (f: f != null) (
(f: f != null) lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions
(lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions); );
format = pkgs.formats.ini { }; format = pkgs.formats.ini { };
definitionsDirectory = utils.systemdUtils.lib.definitions definitionsDirectory = utils.systemdUtils.lib.definitions "repart.d" format (
"repart.d" lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions
format );
(lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions);
mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions; mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
in in
pkgs.callPackage ./repart-image.nix { pkgs.callPackage ./repart-image.nix {
systemd = cfg.package; systemd = cfg.package;
inherit (cfg) name version imageFileBasename compression split seed sectorSize finalPartitions; inherit (cfg)
name
version
imageFileBasename
compression
split
seed
sectorSize
finalPartitions
;
inherit fileSystems definitionsDirectory mkfsEnv; inherit fileSystems definitionsDirectory mkfsEnv;
}; };
meta.maintainers = with lib.maintainers; [ nikstur willibutz ]; meta.maintainers = with lib.maintainers; [
nikstur
willibutz
];
}; };
} }

View file

@ -1,15 +1,21 @@
# This module contains the basic configuration for building a NixOS # This module contains the basic configuration for building a NixOS
# installation CD. # installation CD.
{ config, lib, options, pkgs, ... }:
{ {
imports = config,
[ ./iso-image.nix lib,
options,
pkgs,
...
}:
{
imports = [
./iso-image.nix
# Profiles of this basic installation CD. # Profiles of this basic installation CD.
../../profiles/all-hardware.nix ../../profiles/all-hardware.nix
../../profiles/base.nix ../../profiles/base.nix
../../profiles/installation-device.nix ../../profiles/installation-device.nix
]; ];
# Adds terminus_font for people with HiDPI displays # Adds terminus_font for people with HiDPI displays
console.packages = options.console.packages.default ++ [ pkgs.terminus_font ]; console.packages = options.console.packages.default ++ [ pkgs.terminus_font ];

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,12 @@
# This module creates netboot media containing the given NixOS # This module creates netboot media containing the given NixOS
# configuration. # configuration.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
@ -32,43 +37,50 @@ with lib;
# here and it causes a cyclic dependency. # here and it causes a cyclic dependency.
boot.loader.grub.enable = false; boot.loader.grub.enable = false;
fileSystems."/" = mkImageMediaOverride fileSystems."/" = mkImageMediaOverride {
{ fsType = "tmpfs"; fsType = "tmpfs";
options = [ "mode=0755" ]; options = [ "mode=0755" ];
}; };
# In stage 1, mount a tmpfs on top of /nix/store (the squashfs # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
# image) to make this a live CD. # image) to make this a live CD.
fileSystems."/nix/.ro-store" = mkImageMediaOverride fileSystems."/nix/.ro-store" = mkImageMediaOverride {
{ fsType = "squashfs"; fsType = "squashfs";
device = "../nix-store.squashfs"; device = "../nix-store.squashfs";
options = [ "loop" ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi"; options = [
neededForBoot = true; "loop"
] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
neededForBoot = true;
};
fileSystems."/nix/.rw-store" = mkImageMediaOverride {
fsType = "tmpfs";
options = [ "mode=0755" ];
neededForBoot = true;
};
fileSystems."/nix/store" = mkImageMediaOverride {
overlay = {
lowerdir = [ "/nix/.ro-store" ];
upperdir = "/nix/.rw-store/store";
workdir = "/nix/.rw-store/work";
}; };
neededForBoot = true;
};
fileSystems."/nix/.rw-store" = mkImageMediaOverride boot.initrd.availableKernelModules = [
{ fsType = "tmpfs"; "squashfs"
options = [ "mode=0755" ]; "overlay"
neededForBoot = true; ];
};
fileSystems."/nix/store" = mkImageMediaOverride boot.initrd.kernelModules = [
{ overlay = { "loop"
lowerdir = [ "/nix/.ro-store" ]; "overlay"
upperdir = "/nix/.rw-store/store"; ];
workdir = "/nix/.rw-store/work";
};
neededForBoot = true;
};
boot.initrd.availableKernelModules = [ "squashfs" "overlay" ];
boot.initrd.kernelModules = [ "loop" "overlay" ];
# Closures to be copied to the Nix store, namely the init # Closures to be copied to the Nix store, namely the init
# script and the top-level system configuration directory. # script and the top-level system configuration directory.
netboot.storeContents = netboot.storeContents = [ config.system.build.toplevel ];
[ config.system.build.toplevel ];
# Create the squashfs image that contains the Nix store. # Create the squashfs image that contains the Nix store.
system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix { system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
@ -76,17 +88,17 @@ with lib;
comp = config.netboot.squashfsCompression; comp = config.netboot.squashfsCompression;
}; };
# Create the initrd # Create the initrd
system.build.netbootRamdisk = pkgs.makeInitrdNG { system.build.netbootRamdisk = pkgs.makeInitrdNG {
inherit (config.boot.initrd) compressor; inherit (config.boot.initrd) compressor;
prepend = [ "${config.system.build.initialRamdisk}/initrd" ]; prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
contents = contents = [
[ { source = config.system.build.squashfsStore; {
target = "/nix-store.squashfs"; source = config.system.build.squashfsStore;
} target = "/nix-store.squashfs";
]; }
];
}; };
system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" '' system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
@ -131,17 +143,16 @@ with lib;
boot.loader.timeout = 10; boot.loader.timeout = 10;
boot.postBootCommands = boot.postBootCommands = ''
'' # After booting, register the contents of the Nix store
# After booting, register the contents of the Nix store # in the Nix database in the tmpfs.
# in the Nix database in the tmpfs. ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration
${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration
# nixos-rebuild also requires a "system" profile and an # nixos-rebuild also requires a "system" profile and an
# /etc/NIXOS tag. # /etc/NIXOS tag.
touch /etc/NIXOS touch /etc/NIXOS
${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
''; '';
}; };

View file

@ -1,6 +1,11 @@
# To build, use: # To build, use:
# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-aarch64.nix -A config.system.build.sdImage # nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-aarch64.nix -A config.system.build.sdImage
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
imports = [ imports = [
@ -16,49 +21,55 @@
# The serial ports listed here are: # The serial ports listed here are:
# - ttyS0: for Tegra (Jetson TX1) # - ttyS0: for Tegra (Jetson TX1)
# - ttyAMA0: for QEMU's -machine virt # - ttyAMA0: for QEMU's -machine virt
boot.kernelParams = ["console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"]; boot.kernelParams = [
"console=ttyS0,115200n8"
"console=ttyAMA0,115200n8"
"console=tty0"
];
sdImage = { sdImage = {
populateFirmwareCommands = let populateFirmwareCommands =
configTxt = pkgs.writeText "config.txt" '' let
[pi3] configTxt = pkgs.writeText "config.txt" ''
kernel=u-boot-rpi3.bin [pi3]
kernel=u-boot-rpi3.bin
[pi02] [pi02]
kernel=u-boot-rpi3.bin kernel=u-boot-rpi3.bin
[pi4] [pi4]
kernel=u-boot-rpi4.bin kernel=u-boot-rpi4.bin
enable_gic=1 enable_gic=1
armstub=armstub8-gic.bin armstub=armstub8-gic.bin
# Otherwise the resolution will be weird in most cases, compared to # Otherwise the resolution will be weird in most cases, compared to
# what the pi3 firmware does by default. # what the pi3 firmware does by default.
disable_overscan=1 disable_overscan=1
# Supported in newer board revisions # Supported in newer board revisions
arm_boost=1 arm_boost=1
[cm4] [cm4]
# Enable host mode on the 2711 built-in XHCI USB controller. # Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required # This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required. # (e.g. for USB device mode) or if USB support is not required.
otg_mode=1 otg_mode=1
[all] [all]
# Boot in 64-bit mode. # Boot in 64-bit mode.
arm_64bit=1 arm_64bit=1
# U-Boot needs this to work, regardless of whether UART is actually used or not. # U-Boot needs this to work, regardless of whether UART is actually used or not.
# Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still # Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
# a requirement in the future. # a requirement in the future.
enable_uart=1 enable_uart=1
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
# when attempting to show low-voltage or overtemperature warnings. # when attempting to show low-voltage or overtemperature warnings.
avoid_warnings=1 avoid_warnings=1
''; '';
in '' in
''
(cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/) (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
# Add the config # Add the config

View file

@ -11,24 +11,36 @@
# The derivation for the SD image will be placed in # The derivation for the SD image will be placed in
# config.system.build.sdImage # config.system.build.sdImage
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
let let
rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({ rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix (
inherit (config.sdImage) storePaths; {
compressImage = config.sdImage.compressImage; inherit (config.sdImage) storePaths;
populateImageCommands = config.sdImage.populateRootCommands; compressImage = config.sdImage.compressImage;
volumeLabel = "NIXOS_SD"; populateImageCommands = config.sdImage.populateRootCommands;
} // optionalAttrs (config.sdImage.rootPartitionUUID != null) { volumeLabel = "NIXOS_SD";
uuid = config.sdImage.rootPartitionUUID; }
}); // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
uuid = config.sdImage.rootPartitionUUID;
}
);
in in
{ {
imports = [ imports = [
(mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.") (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ]
(mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.") "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID."
)
(mkRemovedOptionModule [ "sdImage" "bootSize" ]
"The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required."
)
../../profiles/all-hardware.nix ../../profiles/all-hardware.nix
]; ];
@ -169,7 +181,10 @@ in
# Alternatively, this could be removed from the configuration. # Alternatively, this could be removed from the configuration.
# The filesystem is not needed at runtime, it could be treated # The filesystem is not needed at runtime, it could be treated
# as an opaque blob instead of a discrete FAT32 filesystem. # as an opaque blob instead of a discrete FAT32 filesystem.
options = [ "nofail" "noauto" ]; options = [
"nofail"
"noauto"
];
}; };
"/" = { "/" = {
device = "/dev/disk/by-label/NIXOS_SD"; device = "/dev/disk/by-label/NIXOS_SD";
@ -179,122 +194,139 @@ in
sdImage.storePaths = [ config.system.build.toplevel ]; sdImage.storePaths = [ config.system.build.toplevel ];
system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, system.build.sdImage = pkgs.callPackage (
mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation { {
name = config.sdImage.imageName; stdenv,
dosfstools,
e2fsprogs,
mtools,
libfaketime,
util-linux,
zstd,
}:
stdenv.mkDerivation {
name = config.sdImage.imageName;
nativeBuildInputs = [ dosfstools e2fsprogs libfaketime mtools util-linux ] nativeBuildInputs = [
++ lib.optional config.sdImage.compressImage zstd; dosfstools
e2fsprogs
libfaketime
mtools
util-linux
] ++ lib.optional config.sdImage.compressImage zstd;
inherit (config.sdImage) imageName compressImage; inherit (config.sdImage) imageName compressImage;
buildCommand = '' buildCommand = ''
mkdir -p $out/nix-support $out/sd-image mkdir -p $out/nix-support $out/sd-image
export img=$out/sd-image/${config.sdImage.imageName} export img=$out/sd-image/${config.sdImage.imageName}
echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
if test -n "$compressImage"; then if test -n "$compressImage"; then
echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products
else else
echo "file sd-image $img" >> $out/nix-support/hydra-build-products echo "file sd-image $img" >> $out/nix-support/hydra-build-products
fi fi
root_fs=${rootfsImage} root_fs=${rootfsImage}
${lib.optionalString config.sdImage.compressImage '' ${lib.optionalString config.sdImage.compressImage ''
root_fs=./root-fs.img root_fs=./root-fs.img
echo "Decompressing rootfs image" echo "Decompressing rootfs image"
zstd -d --no-progress "${rootfsImage}" -o $root_fs zstd -d --no-progress "${rootfsImage}" -o $root_fs
''} ''}
# Gap in front of the first partition, in MiB # Gap in front of the first partition, in MiB
gap=${toString config.sdImage.firmwarePartitionOffset} gap=${toString config.sdImage.firmwarePartitionOffset}
# Create the image file sized to fit /boot/firmware and /, plus slack for the gap. # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
rootSizeBlocks=$(du -B 512 --apparent-size $root_fs | awk '{ print $1 }') rootSizeBlocks=$(du -B 512 --apparent-size $root_fs | awk '{ print $1 }')
firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512)) firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024)) imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
truncate -s $imageSize $img truncate -s $imageSize $img
# type=b is 'W95 FAT32', type=83 is 'Linux'. # type=b is 'W95 FAT32', type=83 is 'Linux'.
# The "bootable" partition is where u-boot will look file for the bootloader # The "bootable" partition is where u-boot will look file for the bootloader
# information (dtbs, extlinux.conf file). # information (dtbs, extlinux.conf file).
sfdisk --no-reread --no-tell-kernel $img <<EOF sfdisk --no-reread --no-tell-kernel $img <<EOF
label: dos label: dos
label-id: ${config.sdImage.firmwarePartitionID} label-id: ${config.sdImage.firmwarePartitionID}
start=''${gap}M, size=$firmwareSizeBlocks, type=b start=''${gap}M, size=$firmwareSizeBlocks, type=b
start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
EOF EOF
# Copy the rootfs into the SD image # Copy the rootfs into the SD image
eval $(partx $img -o START,SECTORS --nr 2 --pairs) eval $(partx $img -o START,SECTORS --nr 2 --pairs)
dd conv=notrunc if=$root_fs of=$img seek=$START count=$SECTORS dd conv=notrunc if=$root_fs of=$img seek=$START count=$SECTORS
# Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
eval $(partx $img -o START,SECTORS --nr 1 --pairs) eval $(partx $img -o START,SECTORS --nr 1 --pairs)
truncate -s $((SECTORS * 512)) firmware_part.img truncate -s $((SECTORS * 512)) firmware_part.img
mkfs.vfat --invariant -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img mkfs.vfat --invariant -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
# Populate the files intended for /boot/firmware # Populate the files intended for /boot/firmware
mkdir firmware mkdir firmware
${config.sdImage.populateFirmwareCommands} ${config.sdImage.populateFirmwareCommands}
find firmware -exec touch --date=2000-01-01 {} + find firmware -exec touch --date=2000-01-01 {} +
# Copy the populated /boot/firmware into the SD image # Copy the populated /boot/firmware into the SD image
cd firmware cd firmware
# Force a fixed order in mcopy for better determinism, and avoid file globbing # Force a fixed order in mcopy for better determinism, and avoid file globbing
for d in $(find . -type d -mindepth 1 | sort); do for d in $(find . -type d -mindepth 1 | sort); do
faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d" faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d"
done done
for f in $(find . -type f | sort); do for f in $(find . -type f | sort); do
mcopy -pvm -i ../firmware_part.img "$f" "::/$f" mcopy -pvm -i ../firmware_part.img "$f" "::/$f"
done done
cd .. cd ..
# Verify the FAT partition before copying it. # Verify the FAT partition before copying it.
fsck.vfat -vn firmware_part.img fsck.vfat -vn firmware_part.img
dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
${config.sdImage.postBuildCommands} ${config.sdImage.postBuildCommands}
if test -n "$compressImage"; then if test -n "$compressImage"; then
zstd -T$NIX_BUILD_CORES --rm $img zstd -T$NIX_BUILD_CORES --rm $img
fi
'';
}
) { };
boot.postBootCommands =
let
expandOnBoot = lib.optionalString config.sdImage.expandOnBoot ''
# Figure out device names for the boot device and root filesystem.
rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
bootDevice=$(lsblk -npo PKNAME $rootPart)
partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
# Resize the root partition and the filesystem to fit the disk
echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
${pkgs.parted}/bin/partprobe
${pkgs.e2fsprogs}/bin/resize2fs $rootPart
'';
nixPathRegistrationFile = config.sdImage.nixPathRegistrationFile;
in
''
# On the first boot do some maintenance tasks
if [ -f ${nixPathRegistrationFile} ]; then
set -euo pipefail
set -x
${expandOnBoot}
# Register the contents of the initial Nix store
${config.nix.package.out}/bin/nix-store --load-db < ${nixPathRegistrationFile}
# nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
touch /etc/NIXOS
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
# Prevents this from running on later boots.
rm -f ${nixPathRegistrationFile}
fi fi
''; '';
}) {};
boot.postBootCommands = let
expandOnBoot = lib.optionalString config.sdImage.expandOnBoot ''
# Figure out device names for the boot device and root filesystem.
rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
bootDevice=$(lsblk -npo PKNAME $rootPart)
partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
# Resize the root partition and the filesystem to fit the disk
echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
${pkgs.parted}/bin/partprobe
${pkgs.e2fsprogs}/bin/resize2fs $rootPart
'';
nixPathRegistrationFile = config.sdImage.nixPathRegistrationFile;
in ''
# On the first boot do some maintenance tasks
if [ -f ${nixPathRegistrationFile} ]; then
set -euo pipefail
set -x
${expandOnBoot}
# Register the contents of the initial Nix store
${config.nix.package.out}/bin/nix-store --load-db < ${nixPathRegistrationFile}
# nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
touch /etc/NIXOS
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
# Prevents this from running on later boots.
rm -f ${nixPathRegistrationFile}
fi
'';
}; };
} }

View file

@ -1,19 +1,29 @@
# This module generates nixos-install, nixos-rebuild, # This module generates nixos-install, nixos-rebuild,
# nixos-generate-config, etc. # nixos-generate-config, etc.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
makeProg = args: pkgs.substituteAll (args // { makeProg =
dir = "bin"; args:
isExecutable = true; pkgs.substituteAll (
nativeBuildInputs = [ args
pkgs.installShellFiles // {
]; dir = "bin";
postInstall = '' isExecutable = true;
installManPage ${args.manPage} nativeBuildInputs = [
''; pkgs.installShellFiles
}); ];
postInstall = ''
installManPage ${args.manPage}
'';
}
);
nixos-generate-config = makeProg { nixos-generate-config = makeProg {
name = "nixos-generate-config"; name = "nixos-generate-config";
@ -33,13 +43,17 @@ let
inherit (pkgs) runtimeShell; inherit (pkgs) runtimeShell;
inherit (config.system.nixos) version codeName revision; inherit (config.system.nixos) version codeName revision;
inherit (config.system) configurationRevision; inherit (config.system) configurationRevision;
json = builtins.toJSON ({ json = builtins.toJSON (
nixosVersion = config.system.nixos.version; {
} // lib.optionalAttrs (config.system.nixos.revision != null) { nixosVersion = config.system.nixos.version;
nixpkgsRevision = config.system.nixos.revision; }
} // lib.optionalAttrs (config.system.configurationRevision != null) { // lib.optionalAttrs (config.system.nixos.revision != null) {
configurationRevision = config.system.configurationRevision; nixpkgsRevision = config.system.nixos.revision;
}); }
// lib.optionalAttrs (config.system.configurationRevision != null) {
configurationRevision = config.system.configurationRevision;
}
);
manPage = ./manpages/nixos-version.8; manPage = ./manpages/nixos-version.8;
}; };
@ -187,7 +201,7 @@ in
desktopConfiguration = lib.mkOption { desktopConfiguration = lib.mkOption {
internal = true; internal = true;
type = lib.types.listOf lib.types.lines; type = lib.types.listOf lib.types.lines;
default = []; default = [ ];
description = '' description = ''
Text to preseed the desktop configuration that `nixos-generate-config` Text to preseed the desktop configuration that `nixos-generate-config`
saves to `/etc/nixos/configuration.nix`. saves to `/etc/nixos/configuration.nix`.
@ -214,26 +228,46 @@ in
''; '';
}; };
imports = let imports =
mkToolModule = { name, package ? pkgs.${name} }: { config, ... }: { let
options.system.tools.${name}.enable = lib.mkEnableOption "${name} script" // { mkToolModule =
default = config.nix.enable && ! config.system.disableInstallerTools; {
defaultText = "config.nix.enable && !config.system.disableInstallerTools"; name,
}; package ? pkgs.${name},
}:
{ config, ... }:
{
options.system.tools.${name}.enable = lib.mkEnableOption "${name} script" // {
default = config.nix.enable && !config.system.disableInstallerTools;
defaultText = "config.nix.enable && !config.system.disableInstallerTools";
};
config = lib.mkIf config.system.tools.${name}.enable { config = lib.mkIf config.system.tools.${name}.enable {
environment.systemPackages = [ package ]; environment.systemPackages = [ package ];
}; };
}; };
in [ in
(mkToolModule { name = "nixos-build-vms"; }) [
(mkToolModule { name = "nixos-enter"; }) (mkToolModule { name = "nixos-build-vms"; })
(mkToolModule { name = "nixos-generate-config"; package = config.system.build.nixos-generate-config; }) (mkToolModule { name = "nixos-enter"; })
(mkToolModule { name = "nixos-install"; package = config.system.build.nixos-install; }) (mkToolModule {
(mkToolModule { name = "nixos-option"; }) name = "nixos-generate-config";
(mkToolModule { name = "nixos-rebuild"; package = config.system.build.nixos-rebuild; }) package = config.system.build.nixos-generate-config;
(mkToolModule { name = "nixos-version"; package = nixos-version; }) })
]; (mkToolModule {
name = "nixos-install";
package = config.system.build.nixos-install;
})
(mkToolModule { name = "nixos-option"; })
(mkToolModule {
name = "nixos-rebuild";
package = config.system.build.nixos-rebuild;
})
(mkToolModule {
name = "nixos-version";
package = nixos-version;
})
];
config = { config = {
documentation.man.man-db.skipPackages = [ nixos-version ]; documentation.man.man-db.skipPackages = [ nixos-version ];

View file

@ -1,4 +1,16 @@
{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, specialArgs, ... }: {
config,
options,
lib,
pkgs,
utils,
modules,
baseModules,
extraModules,
modulesPath,
specialArgs,
...
}:
let let
inherit (lib) inherit (lib)
@ -31,25 +43,26 @@ let
cfg = config.documentation; cfg = config.documentation;
allOpts = options; allOpts = options;
canCacheDocs = m: canCacheDocs =
m:
let let
f = import m; f = import m;
instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f)); instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f));
in in
cfg.nixos.options.splitBuild cfg.nixos.options.splitBuild
&& isPath m && isPath m
&& isFunction f && isFunction f
&& instance ? options && instance ? options
&& instance.meta.buildDocsInSandbox or true; && instance.meta.buildDocsInSandbox or true;
docModules = docModules =
let let
p = partition canCacheDocs (baseModules ++ cfg.nixos.extraModules); p = partition canCacheDocs (baseModules ++ cfg.nixos.extraModules);
in in
{ {
lazy = p.right; lazy = p.right;
eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules); eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
}; };
manual = import ../../doc/manual rec { manual = import ../../doc/manual rec {
inherit pkgs config; inherit pkgs config;
@ -59,9 +72,11 @@ let
options = options =
let let
scrubbedEval = evalModules { scrubbedEval = evalModules {
modules = [ { modules = [
_module.check = false; {
} ] ++ docModules.eager; _module.check = false;
}
] ++ docModules.eager;
class = "nixos"; class = "nixos";
specialArgs = specialArgs // { specialArgs = specialArgs // {
pkgs = scrubDerivations "pkgs" pkgs; pkgs = scrubDerivations "pkgs" pkgs;
@ -71,33 +86,37 @@ let
inherit modulesPath utils; inherit modulesPath utils;
}; };
}; };
scrubDerivations = namePrefix: pkgSet: mapAttrs scrubDerivations =
(name: value: namePrefix: pkgSet:
mapAttrs (
name: value:
let let
wholeName = "${namePrefix}.${name}"; wholeName = "${namePrefix}.${name}";
guard = warn "Attempt to evaluate package ${wholeName} in option documentation; this is not supported and will eventually be an error. Use `mkPackageOption{,MD}` or `literalExpression` instead."; guard = warn "Attempt to evaluate package ${wholeName} in option documentation; this is not supported and will eventually be an error. Use `mkPackageOption{,MD}` or `literalExpression` instead.";
in if isAttrs value then in
if isAttrs value then
scrubDerivations wholeName value scrubDerivations wholeName value
// optionalAttrs (isDerivation value) { // optionalAttrs (isDerivation value) {
outPath = guard "\${${wholeName}}"; outPath = guard "\${${wholeName}}";
drvPath = guard value.drvPath; drvPath = guard value.drvPath;
} }
else value else
) value
pkgSet; ) pkgSet;
in scrubbedEval.options; in
scrubbedEval.options;
baseOptionsJSON = baseOptionsJSON =
let let
filter = filter = builtins.filterSource (
builtins.filterSource n: t:
(n: t: cleanSourceFilter n t
cleanSourceFilter n t && (t == "directory" -> baseNameOf n != "tests")
&& (t == "directory" -> baseNameOf n != "tests") && (t == "file" -> hasSuffix ".nix" n)
&& (t == "file" -> hasSuffix ".nix" n) );
);
in in
pkgs.runCommand "lazy-options.json" { pkgs.runCommand "lazy-options.json"
{
libPath = filter (pkgs.path + "/lib"); libPath = filter (pkgs.path + "/lib");
pkgsLibPath = filter (pkgs.path + "/pkgs/pkgs-lib"); pkgsLibPath = filter (pkgs.path + "/pkgs/pkgs-lib");
nixosPath = filter (pkgs.path + "/nixos"); nixosPath = filter (pkgs.path + "/nixos");
@ -107,7 +126,8 @@ let
+ concatMapStringsSep " " (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy + concatMapStringsSep " " (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy
+ " ]"; + " ]";
passAsFile = [ "modules" ]; passAsFile = [ "modules" ];
} '' }
''
export NIX_STORE_DIR=$TMPDIR/store export NIX_STORE_DIR=$TMPDIR/store
export NIX_STATE_DIR=$TMPDIR/state export NIX_STATE_DIR=$TMPDIR/state
${pkgs.buildPackages.nix}/bin/nix-instantiate \ ${pkgs.buildPackages.nix}/bin/nix-instantiate \
@ -139,36 +159,37 @@ let
inherit (cfg.nixos.options) warningsAreErrors; inherit (cfg.nixos.options) warningsAreErrors;
}; };
nixos-help =
nixos-help = let let
helpScript = pkgs.writeShellScriptBin "nixos-help" '' helpScript = pkgs.writeShellScriptBin "nixos-help" ''
# Finds first executable browser in a colon-separated list. # Finds first executable browser in a colon-separated list.
# (see how xdg-open defines BROWSER) # (see how xdg-open defines BROWSER)
browser="$( browser="$(
IFS=: ; for b in $BROWSER; do IFS=: ; for b in $BROWSER; do
[ -n "$(type -P "$b" || true)" ] && echo "$b" && break [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
done done
)" )"
if [ -z "$browser" ]; then
browser="$(type -P xdg-open || true)"
if [ -z "$browser" ]; then if [ -z "$browser" ]; then
browser="${pkgs.w3m-nographics}/bin/w3m" browser="$(type -P xdg-open || true)"
if [ -z "$browser" ]; then
browser="${pkgs.w3m-nographics}/bin/w3m"
fi
fi fi
fi exec "$browser" ${manual.manualHTMLIndex}
exec "$browser" ${manual.manualHTMLIndex} '';
'';
desktopItem = pkgs.makeDesktopItem { desktopItem = pkgs.makeDesktopItem {
name = "nixos-manual"; name = "nixos-manual";
desktopName = "NixOS Manual"; desktopName = "NixOS Manual";
genericName = "System Manual"; genericName = "System Manual";
comment = "View NixOS documentation in a web browser"; comment = "View NixOS documentation in a web browser";
icon = "nix-snowflake"; icon = "nix-snowflake";
exec = "nixos-help"; exec = "nixos-help";
categories = ["System"]; categories = [ "System" ];
}; };
in pkgs.symlinkJoin { in
pkgs.symlinkJoin {
name = "nixos-help"; name = "nixos-help";
paths = [ paths = [
helpScript helpScript
@ -187,11 +208,14 @@ in
../config/system-path.nix ../config/system-path.nix
../system/etc/etc.nix ../system/etc/etc.nix
(mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ]) (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
(mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ]) (mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ])
(mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ]) (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
(mkRemovedOptionModule (mkRemovedOptionModule [
[ "documentation" "nixos" "options" "allowDocBook" ] "documentation"
"DocBook option documentation is no longer supported") "nixos"
"options"
"allowDocBook"
] "DocBook option documentation is no longer supported")
]; ];
options = { options = {
@ -280,7 +304,7 @@ in
nixos.extraModules = mkOption { nixos.extraModules = mkOption {
type = types.listOf types.raw; type = types.listOf types.raw;
default = []; default = [ ];
description = '' description = ''
Modules for which to show options even when not imported. Modules for which to show options even when not imported.
''; '';
@ -374,9 +398,13 @@ in
(mkIf cfg.nixos.enable { (mkIf cfg.nixos.enable {
system.build.manual = manual; system.build.manual = manual;
environment.systemPackages = [] environment.systemPackages =
[ ]
++ optional cfg.man.enable manual.nixos-configuration-reference-manpage ++ optional cfg.man.enable manual.nixos-configuration-reference-manpage
++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ]; ++ optionals cfg.doc.enable [
manual.manualHTML
nixos-help
];
}) })
]); ]);

View file

@ -3,7 +3,7 @@
# enabled in the initrd. Its primary use is in the NixOS installation # enabled in the initrd. Its primary use is in the NixOS installation
# CDs. # CDs.
{ pkgs, lib,... }: { pkgs, lib, ... }:
let let
platform = pkgs.stdenv.hostPlatform; platform = pkgs.stdenv.hostPlatform;
in in
@ -12,26 +12,61 @@ in
# The initrd has to contain any module that might be necessary for # The initrd has to contain any module that might be necessary for
# supporting the most important parts of HW like drives. # supporting the most important parts of HW like drives.
boot.initrd.availableKernelModules = boot.initrd.availableKernelModules =
[ # SATA/PATA support. [
# SATA/PATA support.
"ahci" "ahci"
"ata_piix" "ata_piix"
"sata_inic162x" "sata_nv" "sata_promise" "sata_qstor" "sata_inic162x"
"sata_sil" "sata_sil24" "sata_sis" "sata_svw" "sata_sx4" "sata_nv"
"sata_uli" "sata_via" "sata_vsc" "sata_promise"
"sata_qstor"
"sata_sil"
"sata_sil24"
"sata_sis"
"sata_svw"
"sata_sx4"
"sata_uli"
"sata_via"
"sata_vsc"
"pata_ali" "pata_amd" "pata_artop" "pata_atiixp" "pata_efar" "pata_ali"
"pata_hpt366" "pata_hpt37x" "pata_hpt3x2n" "pata_hpt3x3" "pata_amd"
"pata_it8213" "pata_it821x" "pata_jmicron" "pata_marvell" "pata_artop"
"pata_mpiix" "pata_netcell" "pata_ns87410" "pata_oldpiix" "pata_atiixp"
"pata_pcmcia" "pata_pdc2027x" "pata_qdi" "pata_rz1000" "pata_efar"
"pata_serverworks" "pata_sil680" "pata_sis" "pata_hpt366"
"pata_sl82c105" "pata_triflex" "pata_via" "pata_hpt37x"
"pata_hpt3x2n"
"pata_hpt3x3"
"pata_it8213"
"pata_it821x"
"pata_jmicron"
"pata_marvell"
"pata_mpiix"
"pata_netcell"
"pata_ns87410"
"pata_oldpiix"
"pata_pcmcia"
"pata_pdc2027x"
"pata_qdi"
"pata_rz1000"
"pata_serverworks"
"pata_sil680"
"pata_sis"
"pata_sl82c105"
"pata_triflex"
"pata_via"
"pata_winbond" "pata_winbond"
# SCSI support (incomplete). # SCSI support (incomplete).
"3w-9xxx" "3w-xxxx" "aic79xx" "aic7xxx" "arcmsr" "hpsa" "3w-9xxx"
"3w-xxxx"
"aic79xx"
"aic7xxx"
"arcmsr"
"hpsa"
# USB support, especially for booting from USB CD-ROM # USB support, especially for booting from USB CD-ROM
# drives. # drives.
@ -44,20 +79,33 @@ in
"nvme" "nvme"
# Firewire support. Not tested. # Firewire support. Not tested.
"ohci1394" "sbp2" "ohci1394"
"sbp2"
# Virtio (QEMU, KVM etc.) support. # Virtio (QEMU, KVM etc.) support.
"virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "virtio_balloon" "virtio_console" "virtio_net"
"virtio_pci"
"virtio_mmio"
"virtio_blk"
"virtio_scsi"
"virtio_balloon"
"virtio_console"
# VMware support. # VMware support.
"mptspi" "vmxnet3" "vsock" "mptspi"
] ++ lib.optional platform.isx86 "vmw_balloon" "vmxnet3"
"vsock"
]
++ lib.optional platform.isx86 "vmw_balloon"
++ lib.optionals (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) [ ++ lib.optionals (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) [
"vmw_vmci" "vmwgfx" "vmw_vsock_vmci_transport" "vmw_vmci"
"vmwgfx"
"vmw_vsock_vmci_transport"
# Hyper-V support. # Hyper-V support.
"hv_storvsc" "hv_storvsc"
] ++ lib.optionals pkgs.stdenv.hostPlatform.isAarch [ ]
++ lib.optionals pkgs.stdenv.hostPlatform.isAarch [
# Allwinner support # Allwinner support
# Required for early KMS # Required for early KMS
"sun4i-drm" "sun4i-drm"
@ -68,7 +116,8 @@ in
# Broadcom # Broadcom
"vc4" "vc4"
] ++ lib.optionals pkgs.stdenv.hostPlatform.isAarch64 [ ]
++ lib.optionals pkgs.stdenv.hostPlatform.isAarch64 [
# Most of the following falls into two categories: # Most of the following falls into two categories:
# - early KMS / early display # - early KMS / early display
# - early storage (e.g. USB) support # - early storage (e.g. USB) support
@ -111,7 +160,6 @@ in
# Include lots of firmware. # Include lots of firmware.
hardware.enableRedistributableFirmware = true; hardware.enableRedistributableFirmware = true;
imports = imports = [ ../hardware/network/zydas-zd1211.nix ];
[ ../hardware/network/zydas-zd1211.nix ];
} }

View file

@ -1,7 +1,12 @@
# This module defines the software packages included in the "minimal" # This module defines the software packages included in the "minimal"
# installation CD. It might be useful elsewhere. # installation CD. It might be useful elsewhere.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
# Include some utilities that are useful for installing or repairing # Include some utilities that are useful for installing or repairing
@ -43,9 +48,14 @@
]; ];
# Include support for various filesystems and tools to create / manipulate them. # Include support for various filesystems and tools to create / manipulate them.
boot.supportedFilesystems = boot.supportedFilesystems = [
[ "btrfs" "cifs" "f2fs" "ntfs" "vfat" "xfs" ] ++ "btrfs"
lib.optional (lib.meta.availableOn pkgs.stdenv.hostPlatform config.boot.zfs.package) "zfs"; "cifs"
"f2fs"
"ntfs"
"vfat"
"xfs"
] ++ lib.optional (lib.meta.availableOn pkgs.stdenv.hostPlatform config.boot.zfs.package) "zfs";
# Configure host id for ZFS to work # Configure host id for ZFS to work
networking.hostId = lib.mkDefault "8425e349"; networking.hostId = lib.mkDefault "8425e349";

View file

@ -1,23 +1,28 @@
# Provide a basic configuration for installation devices like CDs. # Provide a basic configuration for installation devices like CDs.
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
with lib; with lib;
{ {
imports = imports = [
[ # Enable devices which are usually scanned, because we don't know the # Enable devices which are usually scanned, because we don't know the
# target system. # target system.
../installer/scan/detected.nix ../installer/scan/detected.nix
../installer/scan/not-detected.nix ../installer/scan/not-detected.nix
# Allow "nixos-rebuild" to work properly by providing # Allow "nixos-rebuild" to work properly by providing
# /etc/nixos/configuration.nix. # /etc/nixos/configuration.nix.
./clone-config.nix ./clone-config.nix
# Include a copy of Nixpkgs so that nixos-install works out of # Include a copy of Nixpkgs so that nixos-install works out of
# the box. # the box.
../installer/cd-dvd/channel.nix ../installer/cd-dvd/channel.nix
]; ];
config = { config = {
system.nixos.variant_id = lib.mkDefault "installer"; system.nixos.variant_id = lib.mkDefault "installer";
@ -31,7 +36,11 @@ with lib;
# Use less privileged nixos user # Use less privileged nixos user
users.users.nixos = { users.users.nixos = {
isNormalUser = true; isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" ]; extraGroups = [
"wheel"
"networkmanager"
"video"
];
# Allow the graphical user to login without password # Allow the graphical user to login without password
initialHashedPassword = ""; initialHashedPassword = "";
}; };
@ -52,21 +61,23 @@ with lib;
services.getty.autologinUser = "nixos"; services.getty.autologinUser = "nixos";
# Some more help text. # Some more help text.
services.getty.helpLine = '' services.getty.helpLine =
The "nixos" and "root" accounts have empty passwords. ''
The "nixos" and "root" accounts have empty passwords.
To log in over ssh you must set a password for either "nixos" or "root" To log in over ssh you must set a password for either "nixos" or "root"
with `passwd` (prefix with `sudo` for "root"), or add your public key to with `passwd` (prefix with `sudo` for "root"), or add your public key to
/home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys. /home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys.
If you need a wireless connection, type If you need a wireless connection, type
`sudo systemctl start wpa_supplicant` and configure a `sudo systemctl start wpa_supplicant` and configure a
network using `wpa_cli`. See the NixOS manual for details. network using `wpa_cli`. See the NixOS manual for details.
'' + optionalString config.services.xserver.enable '' ''
+ optionalString config.services.xserver.enable ''
Type `sudo systemctl start display-manager' to Type `sudo systemctl start display-manager' to
start the graphical user interface. start the graphical user interface.
''; '';
# We run sshd by default. Login is only possible after adding a # We run sshd by default. Login is only possible after adding a
# password via "passwd" or by adding a ssh key to ~/.ssh/authorized_keys. # password via "passwd" or by adding a ssh key to ~/.ssh/authorized_keys.
@ -81,7 +92,7 @@ with lib;
# Enable wpa_supplicant, but don't start it by default. # Enable wpa_supplicant, but don't start it by default.
networking.wireless.enable = mkDefault true; networking.wireless.enable = mkDefault true;
networking.wireless.userControlled.enable = true; networking.wireless.userControlled.enable = true;
systemd.services.wpa_supplicant.wantedBy = mkOverride 50 []; systemd.services.wpa_supplicant.wantedBy = mkOverride 50 [ ];
# Tell the Nix evaluator to garbage collect more aggressively. # Tell the Nix evaluator to garbage collect more aggressively.
# This is desirable in memory-constrained environments that don't # This is desirable in memory-constrained environments that don't
@ -97,15 +108,14 @@ with lib;
# To speed up installation a little bit, include the complete # To speed up installation a little bit, include the complete
# stdenv in the Nix store on the CD. # stdenv in the Nix store on the CD.
system.extraDependencies = with pkgs; system.extraDependencies = with pkgs; [
[ stdenv
stdenv stdenvNoCC # for runCommand
stdenvNoCC # for runCommand busybox
busybox jq # for closureInfo
jq # for closureInfo # For boot.initrd.systemd
# For boot.initrd.systemd makeInitrdNGTool
makeInitrdNGTool ];
];
boot.swraid.enable = true; boot.swraid.enable = true;
# remove warning about unset mail # remove warning about unset mail

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
@ -7,13 +12,13 @@ let
cfg = config.programs.fish; cfg = config.programs.fish;
fishAbbrs = lib.concatStringsSep "\n" ( fishAbbrs = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}") lib.mapAttrsToList (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}") cfg.shellAbbrs
cfg.shellAbbrs
); );
fishAliases = lib.concatStringsSep "\n" ( fishAliases = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}") lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}") (
(lib.filterAttrs (k: v: v != null) cfg.shellAliases) lib.filterAttrs (k: v: v != null) cfg.shellAliases
)
); );
envShellInit = pkgs.writeText "shellInit" cfge.shellInit; envShellInit = pkgs.writeText "shellInit" cfge.shellInit;
@ -22,17 +27,19 @@ let
envInteractiveShellInit = pkgs.writeText "interactiveShellInit" cfge.interactiveShellInit; envInteractiveShellInit = pkgs.writeText "interactiveShellInit" cfge.interactiveShellInit;
sourceEnv = file: sourceEnv =
if cfg.useBabelfish then file:
"source /etc/fish/${file}.fish" if cfg.useBabelfish then
else "source /etc/fish/${file}.fish"
'' else
set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $fish_function_path ''
fenv source /etc/fish/foreign-env/${file} > /dev/null set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $fish_function_path
set -e fish_function_path[1] fenv source /etc/fish/foreign-env/${file} > /dev/null
''; set -e fish_function_path[1]
'';
babelfishTranslate = path: name: babelfishTranslate =
path: name:
pkgs.runCommandLocal "${name}.fish" { pkgs.runCommandLocal "${name}.fish" {
nativeBuildInputs = [ pkgs.babelfish ]; nativeBuildInputs = [ pkgs.babelfish ];
} "babelfish < ${path} > $out;"; } "babelfish < ${path} > $out;";
@ -89,7 +96,7 @@ in
}; };
shellAbbrs = lib.mkOption { shellAbbrs = lib.mkOption {
default = {}; default = { };
example = { example = {
gco = "git checkout"; gco = "git checkout";
npu = "nix-prefetch-url"; npu = "nix-prefetch-url";
@ -101,7 +108,7 @@ in
}; };
shellAliases = lib.mkOption { shellAliases = lib.mkOption {
default = {}; default = { };
description = '' description = ''
Set of aliases for fish shell, which overrides {option}`environment.shellAliases`. Set of aliases for fish shell, which overrides {option}`environment.shellAliases`.
See {option}`environment.shellAliases` for an option format description. See {option}`environment.shellAliases` for an option format description.
@ -153,16 +160,16 @@ in
documentation.man.generateCaches = lib.mkDefault true; documentation.man.generateCaches = lib.mkDefault true;
environment = lib.mkMerge [ environment = lib.mkMerge [
(lib.mkIf cfg.useBabelfish (lib.mkIf cfg.useBabelfish {
{ etc."fish/setEnvironment.fish".source =
etc."fish/setEnvironment.fish".source = babelfishTranslate config.system.build.setEnvironment "setEnvironment"; babelfishTranslate config.system.build.setEnvironment "setEnvironment";
etc."fish/shellInit.fish".source = babelfishTranslate envShellInit "shellInit"; etc."fish/shellInit.fish".source = babelfishTranslate envShellInit "shellInit";
etc."fish/loginShellInit.fish".source = babelfishTranslate envLoginShellInit "loginShellInit"; etc."fish/loginShellInit.fish".source = babelfishTranslate envLoginShellInit "loginShellInit";
etc."fish/interactiveShellInit.fish".source = babelfishTranslate envInteractiveShellInit "interactiveShellInit"; etc."fish/interactiveShellInit.fish".source =
}) babelfishTranslate envInteractiveShellInit "interactiveShellInit";
})
(lib.mkIf (!cfg.useBabelfish) (lib.mkIf (!cfg.useBabelfish) {
{
etc."fish/foreign-env/shellInit".source = envShellInit; etc."fish/foreign-env/shellInit".source = envShellInit;
etc."fish/foreign-env/loginShellInit".source = envLoginShellInit; etc."fish/foreign-env/loginShellInit".source = envLoginShellInit;
etc."fish/foreign-env/interactiveShellInit".source = envInteractiveShellInit; etc."fish/foreign-env/interactiveShellInit".source = envInteractiveShellInit;
@ -170,108 +177,119 @@ in
{ {
etc."fish/nixos-env-preinit.fish".text = etc."fish/nixos-env-preinit.fish".text =
if cfg.useBabelfish if cfg.useBabelfish then
then '' ''
# source the NixOS environment config # source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ] if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
source /etc/fish/setEnvironment.fish source /etc/fish/setEnvironment.fish
end end
'' ''
else '' else
# This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently ''
# unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently
set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish
set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions
# source the NixOS environment config # source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ] if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
fenv source ${config.system.build.setEnvironment} fenv source ${config.system.build.setEnvironment}
end end
# clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
set -e fish_function_path set -e fish_function_path
''; '';
} }
{ {
etc."fish/config.fish".text = '' etc."fish/config.fish".text = ''
# /etc/fish/config.fish: DO NOT EDIT -- this file has been generated automatically. # /etc/fish/config.fish: DO NOT EDIT -- this file has been generated automatically.
# if we haven't sourced the general config, do it # if we haven't sourced the general config, do it
if not set -q __fish_nixos_general_config_sourced if not set -q __fish_nixos_general_config_sourced
${sourceEnv "shellInit"} ${sourceEnv "shellInit"}
${cfg.shellInit} ${cfg.shellInit}
# and leave a note so we don't source this config section again from # and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew) # this very shell (children will source the general config anew)
set -g __fish_nixos_general_config_sourced 1 set -g __fish_nixos_general_config_sourced 1
end end
# if we haven't sourced the login config, do it # if we haven't sourced the login config, do it
status is-login; and not set -q __fish_nixos_login_config_sourced status is-login; and not set -q __fish_nixos_login_config_sourced
and begin and begin
${sourceEnv "loginShellInit"} ${sourceEnv "loginShellInit"}
${cfg.loginShellInit} ${cfg.loginShellInit}
# and leave a note so we don't source this config section again from # and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew) # this very shell (children will source the general config anew)
set -g __fish_nixos_login_config_sourced 1 set -g __fish_nixos_login_config_sourced 1
end end
# if we haven't sourced the interactive config, do it # if we haven't sourced the interactive config, do it
status is-interactive; and not set -q __fish_nixos_interactive_config_sourced status is-interactive; and not set -q __fish_nixos_interactive_config_sourced
and begin and begin
${fishAbbrs} ${fishAbbrs}
${fishAliases} ${fishAliases}
${sourceEnv "interactiveShellInit"} ${sourceEnv "interactiveShellInit"}
${cfg.promptInit} ${cfg.promptInit}
${cfg.interactiveShellInit} ${cfg.interactiveShellInit}
# and leave a note so we don't source this config section again from # and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew, # this very shell (children will source the general config anew,
# allowing configuration changes in, e.g, aliases, to propagate) # allowing configuration changes in, e.g, aliases, to propagate)
set -g __fish_nixos_interactive_config_sourced 1 set -g __fish_nixos_interactive_config_sourced 1
end end
''; '';
} }
{ {
etc."fish/generated_completions".source = etc."fish/generated_completions".source =
let let
patchedGenerator = pkgs.stdenv.mkDerivation { patchedGenerator = pkgs.stdenv.mkDerivation {
name = "fish_patched-completion-generator"; name = "fish_patched-completion-generator";
srcs = [ srcs = [
"${cfg.package}/share/fish/tools/create_manpage_completions.py" "${cfg.package}/share/fish/tools/create_manpage_completions.py"
"${cfg.package}/share/fish/tools/deroff.py" "${cfg.package}/share/fish/tools/deroff.py"
]; ];
unpackCmd = "cp $curSrc $(basename $curSrc)"; unpackCmd = "cp $curSrc $(basename $curSrc)";
sourceRoot = "."; sourceRoot = ".";
patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files
dontBuild = true; dontBuild = true;
installPhase = '' installPhase = ''
mkdir -p $out mkdir -p $out
cp * $out/ cp * $out/
''; '';
preferLocalBuild = true; preferLocalBuild = true;
allowSubstitutes = false; allowSubstitutes = false;
}; };
generateCompletions = package: pkgs.runCommandLocal generateCompletions =
( with lib.strings; let package:
storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash pkgs.runCommandLocal
pathName = substring storeLength (stringLength package - storeLength) package; (
in (package.name or pathName) + "_fish-completions") with lib.strings;
( { inherit package; } // let
lib.optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }) storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash
'' pathName = substring storeLength (stringLength package - storeLength) package;
mkdir -p $out in
if [ -d $package/share/man ]; then (package.name or pathName) + "_fish-completions"
find $package/share/man -type f | xargs ${pkgs.python3.pythonOnBuildForHost.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null )
fi (
''; {
in inherit package;
}
// lib.optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }
)
''
mkdir -p $out
if [ -d $package/share/man ]; then
find $package/share/man -type f | xargs ${pkgs.python3.pythonOnBuildForHost.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
fi
'';
in
pkgs.buildEnv { pkgs.buildEnv {
name = "system_fish-completions"; name = "system_fish-completions";
ignoreCollisions = true; ignoreCollisions = true;
@ -281,10 +299,11 @@ in
# include programs that bring their own completions # include programs that bring their own completions
{ {
pathsToLink = [] pathsToLink =
++ lib.optional cfg.vendor.config.enable "/share/fish/vendor_conf.d" [ ]
++ lib.optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d" ++ lib.optional cfg.vendor.config.enable "/share/fish/vendor_conf.d"
++ lib.optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d"; ++ lib.optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d"
++ lib.optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d";
} }
{ systemPackages = [ cfg.package ]; } { systemPackages = [ cfg.package ]; }

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }: {
pkgs,
config,
lib,
...
}:
let let
cfg = config.programs.fzf; cfg = config.programs.fzf;
@ -16,19 +21,24 @@ in
programs = { programs = {
# load after programs.bash.completion.enable # load after programs.bash.completion.enable
bash.promptPluginInit = lib.mkAfter (lib.optionalString cfg.fuzzyCompletion '' bash.promptPluginInit = lib.mkAfter (
source ${pkgs.fzf}/share/fzf/completion.bash lib.optionalString cfg.fuzzyCompletion ''
'' + lib.optionalString cfg.keybindings '' source ${pkgs.fzf}/share/fzf/completion.bash
source ${pkgs.fzf}/share/fzf/key-bindings.bash ''
''); + lib.optionalString cfg.keybindings ''
source ${pkgs.fzf}/share/fzf/key-bindings.bash
''
);
zsh = { zsh = {
interactiveShellInit = lib.optionalString (!config.programs.zsh.ohMyZsh.enable) interactiveShellInit = lib.optionalString (!config.programs.zsh.ohMyZsh.enable) (
(lib.optionalString cfg.fuzzyCompletion '' lib.optionalString cfg.fuzzyCompletion ''
source ${pkgs.fzf}/share/fzf/completion.zsh source ${pkgs.fzf}/share/fzf/completion.zsh
'' + lib.optionalString cfg.keybindings '' ''
source ${pkgs.fzf}/share/fzf/key-bindings.zsh + lib.optionalString cfg.keybindings ''
''); source ${pkgs.fzf}/share/fzf/key-bindings.zsh
''
);
ohMyZsh.plugins = lib.mkIf config.programs.zsh.ohMyZsh.enable [ "fzf" ]; ohMyZsh.plugins = lib.mkIf config.programs.zsh.ohMyZsh.enable [ "fzf" ];
}; };

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.git; cfg = config.programs.git;
@ -19,27 +24,43 @@ in
let let
gitini = attrsOf (attrsOf anything); gitini = attrsOf (attrsOf anything);
in in
either gitini (listOf gitini) // { either gitini (listOf gitini)
merge = loc: defs: // {
merge =
loc: defs:
let let
config = builtins.foldl' config =
(acc: { value, ... }@x: acc // (if builtins.isList value then { builtins.foldl'
ordered = acc.ordered ++ value; (
} else { acc:
unordered = acc.unordered ++ [ x ]; { value, ... }@x:
})) acc
{ // (
ordered = [ ]; if builtins.isList value then
unordered = [ ]; {
} ordered = acc.ordered ++ value;
defs; }
else
{
unordered = acc.unordered ++ [ x ];
}
)
)
{
ordered = [ ];
unordered = [ ];
}
defs;
in in
[ (gitini.merge loc config.unordered) ] ++ config.ordered; [ (gitini.merge loc config.unordered) ] ++ config.ordered;
}; };
default = [ ]; default = [ ];
example = { example = {
init.defaultBranch = "main"; init.defaultBranch = "main";
url."https://github.com/".insteadOf = [ "gh:" "github:" ]; url."https://github.com/".insteadOf = [
"gh:"
"github:"
];
}; };
description = '' description = ''
Configuration to write to /etc/gitconfig. A list can also be Configuration to write to /etc/gitconfig. A list can also be

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
meta.maintainers = [ lib.maintainers.league ]; meta.maintainers = [ lib.maintainers.league ];
@ -23,6 +28,6 @@
config = lib.mkIf config.programs.gphoto2.enable { config = lib.mkIf config.programs.gphoto2.enable {
services.udev.packages = [ pkgs.libgphoto2 ]; services.udev.packages = [ pkgs.libgphoto2 ];
environment.systemPackages = [ pkgs.gphoto2 ]; environment.systemPackages = [ pkgs.gphoto2 ];
users.groups.camera = {}; users.groups.camera = { };
}; };
} }

View file

@ -1,8 +1,14 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
let let
cfg = config.programs.iotop; cfg = config.programs.iotop;
in { in
{
options = { options = {
programs.iotop.enable = lib.mkEnableOption "iotop + setcap wrapper"; programs.iotop.enable = lib.mkEnableOption "iotop + setcap wrapper";
}; };

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }: {
config,
pkgs,
lib,
...
}:
{ {
# interface # interface

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.nano; cfg = config.programs.nano;
@ -37,11 +42,13 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
environment = { environment = {
etc.nanorc.text = (lib.optionalString cfg.syntaxHighlight '' etc.nanorc.text =
# load syntax highlighting files (lib.optionalString cfg.syntaxHighlight ''
include "${cfg.package}/share/nano/*.nanorc" # load syntax highlighting files
include "${cfg.package}/share/nano/extra/*.nanorc" include "${cfg.package}/share/nano/*.nanorc"
'') + cfg.nanorc; include "${cfg.package}/share/nano/extra/*.nanorc"
'')
+ cfg.nanorc;
systemPackages = [ cfg.package ]; systemPackages = [ cfg.package ];
pathsToLink = [ "/share/nano" ]; pathsToLink = [ "/share/nano" ];
}; };

View file

@ -14,7 +14,7 @@ in
programs.qgroundcontrol = { programs.qgroundcontrol = {
enable = lib.mkEnableOption "qgroundcontrol"; enable = lib.mkEnableOption "qgroundcontrol";
package = lib.mkPackageOption pkgs "qgroundcontrol" {}; package = lib.mkPackageOption pkgs "qgroundcontrol" { };
blacklistModemManagerFromTTYUSB = lib.mkOption { blacklistModemManagerFromTTYUSB = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;

View file

@ -1,5 +1,11 @@
# Configuration for the pwdutils suite of tools: passwd, useradd, etc. # Configuration for the pwdutils suite of tools: passwd, useradd, etc.
{ config, lib, utils, pkgs, ... }: {
config,
lib,
utils,
pkgs,
...
}:
let let
cfg = config.security.loginDefs; cfg = config.security.loginDefs;
in in
@ -35,27 +41,37 @@ in
''; '';
type = lib.types.submodule { type = lib.types.submodule {
freeformType = (pkgs.formats.keyValue { }).type; freeformType = (pkgs.formats.keyValue { }).type;
/* There are three different sources for user/group id ranges, each of which gets /*
used by different programs: There are three different sources for user/group id ranges, each of which gets
- The login.defs file, used by the useradd, groupadd and newusers commands used by different programs:
- The update-users-groups.pl file, used by NixOS in the activation phase to - The login.defs file, used by the useradd, groupadd and newusers commands
decide on which ids to use for declaratively defined users without a static - The update-users-groups.pl file, used by NixOS in the activation phase to
id decide on which ids to use for declaratively defined users without a static
- Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used id
by systemd for features like ConditionUser=@system and systemd-sysusers - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used
*/ by systemd for features like ConditionUser=@system and systemd-sysusers
*/
options = { options = {
DEFAULT_HOME = lib.mkOption { DEFAULT_HOME = lib.mkOption {
description = "Indicate if login is allowed if we can't cd to the home directory."; description = "Indicate if login is allowed if we can't cd to the home directory.";
default = "yes"; default = "yes";
type = lib.types.enum [ "yes" "no" ]; type = lib.types.enum [
"yes"
"no"
];
}; };
ENCRYPT_METHOD = lib.mkOption { ENCRYPT_METHOD = lib.mkOption {
description = "This defines the system default encryption algorithm for encrypting passwords."; description = "This defines the system default encryption algorithm for encrypting passwords.";
# The default crypt() method, keep in sync with the PAM default # The default crypt() method, keep in sync with the PAM default
default = "YESCRYPT"; default = "YESCRYPT";
type = lib.types.enum [ "YESCRYPT" "SHA512" "SHA256" "MD5" "DES"]; type = lib.types.enum [
"YESCRYPT"
"SHA512"
"SHA256"
"MD5"
"DES"
];
}; };
SYS_UID_MIN = lib.mkOption { SYS_UID_MIN = lib.mkOption {
@ -180,7 +196,8 @@ in
security.loginDefs.settings.CHFN_RESTRICT = lib.mkIf (cfg.chfnRestrict != null) cfg.chfnRestrict; security.loginDefs.settings.CHFN_RESTRICT = lib.mkIf (cfg.chfnRestrict != null) cfg.chfnRestrict;
environment.systemPackages = lib.optional config.users.mutableUsers cfg.package environment.systemPackages =
lib.optional config.users.mutableUsers cfg.package
++ lib.optional (lib.types.shellPackage.check config.users.defaultUserShell) config.users.defaultUserShell ++ lib.optional (lib.types.shellPackage.check config.users.defaultUserShell) config.users.defaultUserShell
++ lib.optional (cfg.chfnRestrict != null) pkgs.util-linux; ++ lib.optional (cfg.chfnRestrict != null) pkgs.util-linux;
@ -191,7 +208,8 @@ in
toKeyValue = lib.generators.toKeyValue { toKeyValue = lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault { } " "; mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
}; };
in { in
{
# /etc/login.defs: global configuration for pwdutils. # /etc/login.defs: global configuration for pwdutils.
# You cannot login without it! # You cannot login without it!
"login.defs".source = pkgs.writeText "login.defs" (toKeyValue cfg.settings); "login.defs".source = pkgs.writeText "login.defs" (toKeyValue cfg.settings);
@ -241,17 +259,17 @@ in
inherit source; inherit source;
}; };
in in
{ {
su = mkSetuidRoot "${cfg.package.su}/bin/su"; su = mkSetuidRoot "${cfg.package.su}/bin/su";
sg = mkSetuidRoot "${cfg.package.out}/bin/sg"; sg = mkSetuidRoot "${cfg.package.out}/bin/sg";
newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp"; newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp";
newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap"; newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap";
newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap"; newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap";
} }
// lib.optionalAttrs config.users.mutableUsers { // lib.optionalAttrs config.users.mutableUsers {
chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh"; chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh";
passwd = mkSetuidRoot "${cfg.package.out}/bin/passwd"; passwd = mkSetuidRoot "${cfg.package.out}/bin/passwd";
}; };
}) })
]; ];
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.steam; cfg = config.programs.steam;
@ -6,9 +11,12 @@ let
extraCompatPaths = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages; extraCompatPaths = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
steam-gamescope = let steam-gamescope =
exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env); let
in exports = builtins.attrValues (
builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env
);
in
pkgs.writeShellScriptBin "steam-gamescope" '' pkgs.writeShellScriptBin "steam-gamescope" ''
${builtins.concatStringsSep "\n" exports} ${builtins.concatStringsSep "\n" exports}
gamescope --steam ${builtins.toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf gamescope --steam ${builtins.toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf
@ -21,8 +29,12 @@ let
Comment=A digital distribution platform Comment=A digital distribution platform
Exec=${steam-gamescope}/bin/steam-gamescope Exec=${steam-gamescope}/bin/steam-gamescope
Type=Application Type=Application
'').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; }); '').overrideAttrs
in { (_: {
passthru.providedSessions = [ "steam" ];
});
in
{
options.programs.steam = { options.programs.steam = {
enable = lib.mkEnableOption "steam"; enable = lib.mkEnableOption "steam";
@ -42,27 +54,40 @@ in {
]; ];
} }
''; '';
apply = steam: steam.override (prev: { apply =
extraEnv = (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) { steam:
STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths; steam.override (
}) // (lib.optionalAttrs cfg.extest.enable { prev:
LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so"; {
}) // (prev.extraEnv or {}); extraEnv =
extraLibraries = pkgs: let (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ]; STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths;
additionalLibs = with config.hardware.graphics; })
if pkgs.stdenv.hostPlatform.is64bit // (lib.optionalAttrs cfg.extest.enable {
then [ package ] ++ extraPackages LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
else [ package32 ] ++ extraPackages32; })
in prevLibs ++ additionalLibs; // (prev.extraEnv or { });
extraPkgs = p: (cfg.extraPackages ++ lib.optionals (prev ? extraPkgs) (prev.extraPkgs p)); extraLibraries =
} // lib.optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) pkgs:
{ let
buildFHSEnv = pkgs.buildFHSEnv.override { prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
# use the setuid wrapped bubblewrap additionalLibs =
bubblewrap = "${config.security.wrapperDir}/.."; with config.hardware.graphics;
}; if pkgs.stdenv.hostPlatform.is64bit then
}); [ package ] ++ extraPackages
else
[ package32 ] ++ extraPackages32;
in
prevLibs ++ additionalLibs;
extraPkgs = p: (cfg.extraPackages ++ lib.optionals (prev ? extraPkgs) (prev.extraPkgs p));
}
// lib.optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
buildFHSEnv = pkgs.buildFHSEnv.override {
# use the setuid wrapped bubblewrap
bubblewrap = "${config.security.wrapperDir}/..";
};
}
);
description = '' description = ''
The Steam package to use. Additional libraries are added from the system The Steam package to use. Additional libraries are added from the system
configuration to ensure graphics work properly. configuration to ensure graphics work properly.
@ -141,7 +166,7 @@ in {
gamescopeSession = lib.mkOption { gamescopeSession = lib.mkOption {
description = "Run a GameScope driven Steam session from your display-manager"; description = "Run a GameScope driven Steam session from your display-manager";
default = {}; default = { };
type = lib.types.submodule { type = lib.types.submodule {
options = { options = {
enable = lib.mkEnableOption "GameScope Session"; enable = lib.mkEnableOption "GameScope Session";
@ -176,7 +201,8 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
hardware.graphics = { # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932 hardware.graphics = {
# this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
enable = true; enable = true;
enable32Bit = true; enable32Bit = true;
}; };
@ -194,7 +220,9 @@ in {
programs.steam.extraPackages = cfg.fontPackages; programs.steam.extraPackages = cfg.fontPackages;
programs.gamescope.enable = lib.mkDefault cfg.gamescopeSession.enable; programs.gamescope.enable = lib.mkDefault cfg.gamescopeSession.enable;
services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ]; services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [
gamescopeSessionFile
];
# enable 32bit pulseaudio/pipewire support if needed # enable 32bit pulseaudio/pipewire support if needed
hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable; hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
@ -202,11 +230,15 @@ in {
hardware.steam-hardware.enable = true; hardware.steam-hardware.enable = true;
environment.systemPackages = [ environment.systemPackages =
cfg.package [
cfg.package.run cfg.package
] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope cfg.package.run
++ lib.optional cfg.protontricks.enable (cfg.protontricks.package.override { inherit extraCompatPaths; }); ]
++ lib.optional cfg.gamescopeSession.enable steam-gamescope
++ lib.optional cfg.protontricks.enable (
cfg.protontricks.package.override { inherit extraCompatPaths; }
);
networking.firewall = lib.mkMerge [ networking.firewall = lib.mkMerge [
(lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) { (lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
@ -215,7 +247,12 @@ in {
(lib.mkIf cfg.remotePlay.openFirewall { (lib.mkIf cfg.remotePlay.openFirewall {
allowedTCPPorts = [ 27036 ]; allowedTCPPorts = [ 27036 ];
allowedUDPPortRanges = [ { from = 27031; to = 27035; } ]; allowedUDPPortRanges = [
{
from = 27031;
to = 27035;
}
];
}) })
(lib.mkIf cfg.dedicatedServer.openFirewall { (lib.mkIf cfg.dedicatedServer.openFirewall {

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.programs.sway; cfg = config.programs.sway;
@ -14,33 +19,43 @@ in
<https://github.com/swaywm/sway/wiki> and <https://github.com/swaywm/sway/wiki> and
"man 5 sway" for more information''; "man 5 sway" for more information'';
package = lib.mkPackageOption pkgs "sway" { package =
nullable = true; lib.mkPackageOption pkgs "sway" {
extraDescription = '' nullable = true;
If the package is not overridable with `extraSessionCommands`, `extraOptions`, extraDescription = ''
`withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`, If the package is not overridable with `extraSessionCommands`, `extraOptions`,
then the module options {option}`wrapperFeatures`, {option}`extraSessionCommands`, `withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`,
{option}`extraOptions` and {option}`xwayland` will have no effect. then the module options {option}`wrapperFeatures`, {option}`extraSessionCommands`,
{option}`extraOptions` and {option}`xwayland` will have no effect.
Set to `null` to not add any Sway package to your path. Set to `null` to not add any Sway package to your path.
This should be done if you want to use the Home Manager Sway module to install Sway. This should be done if you want to use the Home Manager Sway module to install Sway.
''; '';
} // { }
apply = p: if p == null then null else // {
wayland-lib.genFinalPackage p { apply =
extraSessionCommands = cfg.extraSessionCommands; p:
extraOptions = cfg.extraOptions; if p == null then
withBaseWrapper = cfg.wrapperFeatures.base; null
withGtkWrapper = cfg.wrapperFeatures.gtk; else
enableXWayland = cfg.xwayland.enable; wayland-lib.genFinalPackage p {
isNixOS = true; extraSessionCommands = cfg.extraSessionCommands;
}; extraOptions = cfg.extraOptions;
}; withBaseWrapper = cfg.wrapperFeatures.base;
withGtkWrapper = cfg.wrapperFeatures.gtk;
enableXWayland = cfg.xwayland.enable;
isNixOS = true;
};
};
wrapperFeatures = { wrapperFeatures = {
base = lib.mkEnableOption '' base =
the base wrapper to execute extra session commands and prepend a lib.mkEnableOption ''
dbus-run-session to the sway command'' // { default = true; }; the base wrapper to execute extra session commands and prepend a
dbus-run-session to the sway command''
// {
default = true;
};
gtk = lib.mkEnableOption '' gtk = lib.mkEnableOption ''
the wrapGAppsHook wrapper to execute sway with required environment the wrapGAppsHook wrapper to execute sway with required environment
variables for GTK applications''; variables for GTK applications'';
@ -69,7 +84,7 @@ in
extraOptions = lib.mkOption { extraOptions = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = []; default = [ ];
example = [ example = [
"--verbose" "--verbose"
"--debug" "--debug"
@ -81,12 +96,22 @@ in
''; '';
}; };
xwayland.enable = lib.mkEnableOption "XWayland" // { default = true; }; xwayland.enable = lib.mkEnableOption "XWayland" // {
default = true;
};
extraPackages = lib.mkOption { extraPackages = lib.mkOption {
type = with lib.types; listOf package; type = with lib.types; listOf package;
# Packages used in default config # Packages used in default config
default = with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ]; default = with pkgs; [
brightnessctl
foot
grim
pulseaudio
swayidle
swaylock
wmenu
];
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ]; with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ];
''; '';
@ -102,69 +127,76 @@ in
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
assertions = [ {
{ assertions = [
assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base; {
message = '' assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled. message = ''
''; The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled.
} '';
]; }
];
environment = { environment = {
systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages; systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
# Needed for the default wallpaper: # Needed for the default wallpaper:
pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway"; pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway";
etc = { etc =
"sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" '' {
# Import the most important environment variables into the D-Bus and systemd "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
# user environments (e.g. required for screen sharing and Pinentry prompts): # Import the most important environment variables into the D-Bus and systemd
exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP # user environments (e.g. required for screen sharing and Pinentry prompts):
# enable systemd-integration exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
exec "systemctl --user import-environment {,WAYLAND_}DISPLAY SWAYSOCK; systemctl --user start sway-session.target" # enable systemd-integration
exec swaymsg -t subscribe '["shutdown"]' && systemctl --user stop sway-session.target exec "systemctl --user import-environment {,WAYLAND_}DISPLAY SWAYSOCK; systemctl --user start sway-session.target"
''; exec swaymsg -t subscribe '["shutdown"]' && systemctl --user stop sway-session.target
} // lib.optionalAttrs (cfg.package != null) { '';
"sway/config".source = lib.mkOptionDefault "${cfg.package}/etc/sway/config"; }
// lib.optionalAttrs (cfg.package != null) {
"sway/config".source = lib.mkOptionDefault "${cfg.package}/etc/sway/config";
};
}; };
};
systemd.user.targets.sway-session = { systemd.user.targets.sway-session = {
description = "sway compositor session"; description = "sway compositor session";
documentation = [ "man:systemd.special(7)" ]; documentation = [ "man:systemd.special(7)" ];
bindsTo = [ "graphical-session.target" ]; bindsTo = [ "graphical-session.target" ];
wants = [ "graphical-session-pre.target" ]; wants = [ "graphical-session-pre.target" ];
after = [ "graphical-session-pre.target" ]; after = [ "graphical-session-pre.target" ];
}; };
# To make a Sway session available if a display manager like SDDM is enabled: # To make a Sway session available if a display manager like SDDM is enabled:
services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package; services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
# https://github.com/emersion/xdg-desktop-portal-wlr/blob/master/contrib/wlroots-portals.conf # https://github.com/emersion/xdg-desktop-portal-wlr/blob/master/contrib/wlroots-portals.conf
# https://github.com/emersion/xdg-desktop-portal-wlr/pull/315 # https://github.com/emersion/xdg-desktop-portal-wlr/pull/315
xdg.portal.config.sway = { xdg.portal.config.sway = {
# Use xdg-desktop-portal-gtk for every portal interface... # Use xdg-desktop-portal-gtk for every portal interface...
default = [ "gtk" ]; default = [ "gtk" ];
# ... except for the ScreenCast, Screenshot and Secret # ... except for the ScreenCast, Screenshot and Secret
"org.freedesktop.impl.portal.ScreenCast" = "wlr"; "org.freedesktop.impl.portal.ScreenCast" = "wlr";
"org.freedesktop.impl.portal.Screenshot" = "wlr"; "org.freedesktop.impl.portal.Screenshot" = "wlr";
# ignore inhibit bc gtk portal always returns as success, # ignore inhibit bc gtk portal always returns as success,
# despite sway/the wlr portal not having an implementation, # despite sway/the wlr portal not having an implementation,
# stopping firefox from using wayland idle-inhibit # stopping firefox from using wayland idle-inhibit
"org.freedesktop.impl.portal.Inhibit" = "none"; "org.freedesktop.impl.portal.Inhibit" = "none";
}; };
} }
(import ./wayland-session.nix { (import ./wayland-session.nix {
inherit lib pkgs; inherit lib pkgs;
enableXWayland = cfg.xwayland.enable; enableXWayland = cfg.xwayland.enable;
}) })
]); ]
);
meta.maintainers = with lib.maintainers; [ primeos colemickens ]; meta.maintainers = with lib.maintainers; [
primeos
colemickens
];
} }

View file

@ -1,6 +1,11 @@
# This module defines global configuration for the xonsh. # This module defines global configuration for the xonsh.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let

View file

@ -1,6 +1,12 @@
# This module defines global configuration for the zshell. # This module defines global configuration for the zshell.
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
let let
@ -10,8 +16,9 @@ let
opt = options.programs.zsh; opt = options.programs.zsh;
zshAliases = builtins.concatStringsSep "\n" ( zshAliases = builtins.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "alias -- ${k}=${lib.escapeShellArg v}") lib.mapAttrsToList (k: v: "alias -- ${k}=${lib.escapeShellArg v}") (
(lib.filterAttrs (k: v: v != null) cfg.shellAliases) lib.filterAttrs (k: v: v != null) cfg.shellAliases
)
); );
zshStartupNotes = '' zshStartupNotes = ''
@ -121,7 +128,10 @@ in
"SHARE_HISTORY" "SHARE_HISTORY"
"HIST_FCNTL_LOCK" "HIST_FCNTL_LOCK"
]; ];
example = [ "EXTENDED_HISTORY" "RM_STAR_WAIT" ]; example = [
"EXTENDED_HISTORY"
"RM_STAR_WAIT"
];
description = '' description = ''
Configure zsh options. See Configure zsh options. See
{manpage}`zshoptions(1)`. {manpage}`zshoptions(1)`.
@ -173,144 +183,141 @@ in
programs.zsh.shellAliases = builtins.mapAttrs (name: lib.mkDefault) cfge.shellAliases; programs.zsh.shellAliases = builtins.mapAttrs (name: lib.mkDefault) cfge.shellAliases;
environment.etc.zshenv.text = environment.etc.zshenv.text = ''
'' # /etc/zshenv: DO NOT EDIT -- this file has been generated automatically.
# /etc/zshenv: DO NOT EDIT -- this file has been generated automatically. # This file is read for all shells.
# This file is read for all shells.
# Only execute this file once per shell. # Only execute this file once per shell.
if [ -n "''${__ETC_ZSHENV_SOURCED-}" ]; then return; fi if [ -n "''${__ETC_ZSHENV_SOURCED-}" ]; then return; fi
__ETC_ZSHENV_SOURCED=1 __ETC_ZSHENV_SOURCED=1
if [ -z "''${__NIXOS_SET_ENVIRONMENT_DONE-}" ]; then if [ -z "''${__NIXOS_SET_ENVIRONMENT_DONE-}" ]; then
. ${config.system.build.setEnvironment} . ${config.system.build.setEnvironment}
fi fi
HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help" HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
# Tell zsh how to find installed completions. # Tell zsh how to find installed completions.
for p in ''${(z)NIX_PROFILES}; do for p in ''${(z)NIX_PROFILES}; do
fpath=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions $fpath) fpath=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions $fpath)
done done
# Setup custom shell init stuff. # Setup custom shell init stuff.
${cfge.shellInit} ${cfge.shellInit}
${cfg.shellInit} ${cfg.shellInit}
# Read system-wide modifications. # Read system-wide modifications.
if test -f /etc/zshenv.local; then if test -f /etc/zshenv.local; then
. /etc/zshenv.local . /etc/zshenv.local
fi fi
''; '';
environment.etc.zprofile.text = environment.etc.zprofile.text = ''
'' # /etc/zprofile: DO NOT EDIT -- this file has been generated automatically.
# /etc/zprofile: DO NOT EDIT -- this file has been generated automatically. # This file is read for login shells.
# This file is read for login shells. #
# ${zshStartupNotes}
${zshStartupNotes}
# Only execute this file once per shell. # Only execute this file once per shell.
if [ -n "''${__ETC_ZPROFILE_SOURCED-}" ]; then return; fi if [ -n "''${__ETC_ZPROFILE_SOURCED-}" ]; then return; fi
__ETC_ZPROFILE_SOURCED=1 __ETC_ZPROFILE_SOURCED=1
# Setup custom login shell init stuff. # Setup custom login shell init stuff.
${cfge.loginShellInit} ${cfge.loginShellInit}
${cfg.loginShellInit} ${cfg.loginShellInit}
# Read system-wide modifications. # Read system-wide modifications.
if test -f /etc/zprofile.local; then if test -f /etc/zprofile.local; then
. /etc/zprofile.local . /etc/zprofile.local
fi fi
''; '';
environment.etc.zshrc.text = environment.etc.zshrc.text = ''
'' # /etc/zshrc: DO NOT EDIT -- this file has been generated automatically.
# /etc/zshrc: DO NOT EDIT -- this file has been generated automatically. # This file is read for interactive shells.
# This file is read for interactive shells. #
# ${zshStartupNotes}
${zshStartupNotes}
# Only execute this file once per shell. # Only execute this file once per shell.
if [ -n "$__ETC_ZSHRC_SOURCED" -o -n "$NOSYSZSHRC" ]; then return; fi if [ -n "$__ETC_ZSHRC_SOURCED" -o -n "$NOSYSZSHRC" ]; then return; fi
__ETC_ZSHRC_SOURCED=1 __ETC_ZSHRC_SOURCED=1
${lib.optionalString (cfg.setOptions != []) '' ${lib.optionalString (cfg.setOptions != [ ]) ''
# Set zsh options. # Set zsh options.
setopt ${builtins.concatStringsSep " " cfg.setOptions} setopt ${builtins.concatStringsSep " " cfg.setOptions}
''} ''}
# Alternative method of determining short and full hostname. # Alternative method of determining short and full hostname.
HOST=${config.networking.fqdnOrHostName} HOST=${config.networking.fqdnOrHostName}
# Setup command line history. # Setup command line history.
# Don't export these, otherwise other shells (bash) will try to use same HISTFILE. # Don't export these, otherwise other shells (bash) will try to use same HISTFILE.
SAVEHIST=${builtins.toString cfg.histSize} SAVEHIST=${builtins.toString cfg.histSize}
HISTSIZE=${builtins.toString cfg.histSize} HISTSIZE=${builtins.toString cfg.histSize}
HISTFILE=${cfg.histFile} HISTFILE=${cfg.histFile}
# Configure sane keyboard defaults. # Configure sane keyboard defaults.
. /etc/zinputrc . /etc/zinputrc
${lib.optionalString cfg.enableGlobalCompInit '' ${lib.optionalString cfg.enableGlobalCompInit ''
# Enable autocompletion. # Enable autocompletion.
autoload -U compinit && compinit autoload -U compinit && compinit
''} ''}
${lib.optionalString cfg.enableBashCompletion '' ${lib.optionalString cfg.enableBashCompletion ''
# Enable compatibility with bash's completion system. # Enable compatibility with bash's completion system.
autoload -U bashcompinit && bashcompinit autoload -U bashcompinit && bashcompinit
''} ''}
# Setup custom interactive shell init stuff. # Setup custom interactive shell init stuff.
${cfge.interactiveShellInit} ${cfge.interactiveShellInit}
${cfg.interactiveShellInit} ${cfg.interactiveShellInit}
${lib.optionalString cfg.enableLsColors '' ${lib.optionalString cfg.enableLsColors ''
# Extra colors for directory listings. # Extra colors for directory listings.
eval "$(${pkgs.coreutils}/bin/dircolors -b)" eval "$(${pkgs.coreutils}/bin/dircolors -b)"
''} ''}
# Setup aliases. # Setup aliases.
${zshAliases} ${zshAliases}
# Setup prompt. # Setup prompt.
${cfg.promptInit} ${cfg.promptInit}
# Disable some features to support TRAMP. # Disable some features to support TRAMP.
if [ "$TERM" = dumb ]; then if [ "$TERM" = dumb ]; then
unsetopt zle prompt_cr prompt_subst unsetopt zle prompt_cr prompt_subst
unset RPS1 RPROMPT unset RPS1 RPROMPT
PS1='$ ' PS1='$ '
PROMPT='$ ' PROMPT='$ '
fi fi
# Read system-wide modifications. # Read system-wide modifications.
if test -f /etc/zshrc.local; then if test -f /etc/zshrc.local; then
. /etc/zshrc.local . /etc/zshrc.local
fi fi
''; '';
# Bug in nix flakes: # Bug in nix flakes:
# If we use `.source` here the path is garbage collected also we point to it with a symlink # If we use `.source` here the path is garbage collected also we point to it with a symlink
# see https://github.com/NixOS/nixpkgs/issues/132732 # see https://github.com/NixOS/nixpkgs/issues/132732
environment.etc.zinputrc.text = builtins.readFile ./zinputrc; environment.etc.zinputrc.text = builtins.readFile ./zinputrc;
environment.systemPackages = [ pkgs.zsh ] environment.systemPackages = [
++ lib.optional cfg.enableCompletion pkgs.nix-zsh-completions; pkgs.zsh
] ++ lib.optional cfg.enableCompletion pkgs.nix-zsh-completions;
environment.pathsToLink = lib.optional cfg.enableCompletion "/share/zsh"; environment.pathsToLink = lib.optional cfg.enableCompletion "/share/zsh";
#users.defaultUserShell = lib.mkDefault "/run/current-system/sw/bin/zsh"; #users.defaultUserShell = lib.mkDefault "/run/current-system/sw/bin/zsh";
environment.shells = environment.shells = [
[ "/run/current-system/sw/bin/zsh"
"/run/current-system/sw/bin/zsh" "${pkgs.zsh}/bin/zsh"
"${pkgs.zsh}/bin/zsh" ];
];
}; };

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,31 @@
lib: lib:
{ cert, groups, services }: {
cert,
groups,
services,
}:
let let
catSep = builtins.concatStringsSep; catSep = builtins.concatStringsSep;
svcGroups = svc: svcGroups =
svc:
(lib.optional (svc.serviceConfig ? Group) svc.serviceConfig.Group) (lib.optional (svc.serviceConfig ? Group) svc.serviceConfig.Group)
++ (svc.serviceConfig.SupplementaryGroups or [ ]); ++ (svc.serviceConfig.SupplementaryGroups or [ ]);
in in
{ {
assertion = builtins.all (svc: assertion = builtins.all (
svc:
svc.serviceConfig.User or "root" == "root" svc.serviceConfig.User or "root" == "root"
|| builtins.elem svc.serviceConfig.User groups.${cert.group}.members || builtins.elem svc.serviceConfig.User groups.${cert.group}.members
|| builtins.elem cert.group (svcGroups svc) || builtins.elem cert.group (svcGroups svc)
) services; ) services;
message = "Certificate ${cert.domain} (group=${cert.group}) must be readable by service(s) ${ message = "Certificate ${cert.domain} (group=${cert.group}) must be readable by service(s) ${
catSep ", " (map (svc: "${svc.name} (user=${svc.serviceConfig.User} groups=${catSep " " (svcGroups svc)})") services) catSep ", " (
map (
svc: "${svc.name} (user=${svc.serviceConfig.User} groups=${catSep " " (svcGroups svc)})"
) services
)
}"; }";
} }

View file

@ -1,23 +1,45 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
let let
inherit (builtins) attrNames head map match readFile; inherit (builtins)
attrNames
head
map
match
readFile
;
inherit (lib) types; inherit (lib) types;
inherit (config.environment) etc; inherit (config.environment) etc;
cfg = config.security.apparmor; cfg = config.security.apparmor;
mkDisableOption = name: mkEnableOption name // { mkDisableOption =
default = true; name:
example = false; mkEnableOption name
}; // {
default = true;
example = false;
};
enabledPolicies = filterAttrs (n: p: p.enable) cfg.policies; enabledPolicies = filterAttrs (n: p: p.enable) cfg.policies;
in in
{ {
imports = [ imports = [
(mkRemovedOptionModule [ "security" "apparmor" "confineSUIDApplications" ] "Please use the new options: `security.apparmor.policies.<policy>.enable'.") (mkRemovedOptionModule [
(mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new option: `security.apparmor.policies'.") "security"
"apparmor"
"confineSUIDApplications"
] "Please use the new options: `security.apparmor.policies.<policy>.enable'.")
(mkRemovedOptionModule [
"security"
"apparmor"
"profiles"
] "Please use the new option: `security.apparmor.policies'.")
apparmor/includes.nix apparmor/includes.nix
apparmor/profiles.nix apparmor/profiles.nix
]; ];
@ -45,22 +67,27 @@ in
description = '' description = ''
AppArmor policies. AppArmor policies.
''; '';
type = types.attrsOf (types.submodule ({ name, config, ... }: { type = types.attrsOf (
options = { types.submodule (
enable = mkDisableOption "loading of the profile into the kernel"; { name, config, ... }:
enforce = mkDisableOption "enforcing of the policy or only complain in the logs"; {
profile = mkOption { options = {
description = "The policy of the profile."; enable = mkDisableOption "loading of the profile into the kernel";
type = types.lines; enforce = mkDisableOption "enforcing of the policy or only complain in the logs";
apply = pkgs.writeText name; profile = mkOption {
}; description = "The policy of the profile.";
}; type = types.lines;
})); apply = pkgs.writeText name;
default = {}; };
};
}
)
);
default = { };
}; };
includes = mkOption { includes = mkOption {
type = types.attrsOf types.lines; type = types.attrsOf types.lines;
default = {}; default = { };
description = '' description = ''
List of paths to be added to AppArmor's searched paths List of paths to be added to AppArmor's searched paths
when resolving `include` directives. when resolving `include` directives.
@ -69,7 +96,7 @@ in
}; };
packages = mkOption { packages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = []; default = [ ];
description = "List of packages to be added to AppArmor's include path"; description = "List of packages to be added to AppArmor's include path";
}; };
enableCache = mkEnableOption '' enableCache = mkEnableOption ''
@ -93,13 +120,12 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = map (policy: assertions = map (policy: {
{ assertion = match ".*/.*" policy == null; assertion = match ".*/.*" policy == null;
message = "`security.apparmor.policies.\"${policy}\"' must not contain a slash."; message = "`security.apparmor.policies.\"${policy}\"' must not contain a slash.";
# Because, for instance, aa-remove-unknown uses profiles_names_list() in rc.apparmor.functions # Because, for instance, aa-remove-unknown uses profiles_names_list() in rc.apparmor.functions
# which does not recurse into sub-directories. # which does not recurse into sub-directories.
} }) (attrNames cfg.policies);
) (attrNames cfg.policies);
environment.systemPackages = [ environment.systemPackages = [
pkgs.apparmor-utils pkgs.apparmor-utils
@ -108,67 +134,81 @@ in
environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" ( environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" (
# It's important to put only enabledPolicies here and not all cfg.policies # It's important to put only enabledPolicies here and not all cfg.policies
# because aa-remove-unknown reads profiles from all /etc/apparmor.d/* # because aa-remove-unknown reads profiles from all /etc/apparmor.d/*
mapAttrsToList (name: p: { inherit name; path = p.profile; }) enabledPolicies ++ mapAttrsToList (name: p: {
mapAttrsToList (name: path: { inherit name path; }) cfg.includes inherit name;
path = p.profile;
}) enabledPolicies
++ mapAttrsToList (name: path: { inherit name path; }) cfg.includes
); );
environment.etc."apparmor/parser.conf".text = '' environment.etc."apparmor/parser.conf".text =
''
${if cfg.enableCache then "write-cache" else "skip-cache"} ${if cfg.enableCache then "write-cache" else "skip-cache"}
cache-loc /var/cache/apparmor cache-loc /var/cache/apparmor
Include /etc/apparmor.d Include /etc/apparmor.d
'' + ''
concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages; + concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages;
# For aa-logprof # For aa-logprof
environment.etc."apparmor/apparmor.conf".text = '' environment.etc."apparmor/apparmor.conf".text = '''';
'';
# For aa-logprof # For aa-logprof
environment.etc."apparmor/severity.db".source = pkgs.apparmor-utils + "/etc/apparmor/severity.db"; environment.etc."apparmor/severity.db".source = pkgs.apparmor-utils + "/etc/apparmor/severity.db";
environment.etc."apparmor/logprof.conf".source = pkgs.runCommand "logprof.conf" { environment.etc."apparmor/logprof.conf".source =
header = '' pkgs.runCommand "logprof.conf"
[settings] {
# /etc/apparmor.d/ is read-only on NixOS header = ''
profiledir = /var/cache/apparmor/logprof [settings]
inactive_profiledir = /etc/apparmor.d/disable # /etc/apparmor.d/ is read-only on NixOS
# Use: journalctl -b --since today --grep audit: | aa-logprof profiledir = /var/cache/apparmor/logprof
logfiles = /dev/stdin inactive_profiledir = /etc/apparmor.d/disable
# Use: journalctl -b --since today --grep audit: | aa-logprof
logfiles = /dev/stdin
parser = ${pkgs.apparmor-parser}/bin/apparmor_parser parser = ${pkgs.apparmor-parser}/bin/apparmor_parser
ldd = ${pkgs.glibc.bin}/bin/ldd ldd = ${pkgs.glibc.bin}/bin/ldd
logger = ${pkgs.util-linux}/bin/logger logger = ${pkgs.util-linux}/bin/logger
# customize how file ownership permissions are presented # customize how file ownership permissions are presented
# 0 - off # 0 - off
# 1 - default of what ever mode the log reported # 1 - default of what ever mode the log reported
# 2 - force the new permissions to be user # 2 - force the new permissions to be user
# 3 - force all perms on the rule to be user # 3 - force all perms on the rule to be user
default_owner_prompt = 1 default_owner_prompt = 1
custom_includes = /etc/apparmor.d ${concatMapStringsSep " " (p: "${p}/etc/apparmor.d") cfg.packages} custom_includes = /etc/apparmor.d ${
concatMapStringsSep " " (p: "${p}/etc/apparmor.d") cfg.packages
}
[qualifiers] [qualifiers]
${pkgs.runtimeShell} = icnu ${pkgs.runtimeShell} = icnu
${pkgs.bashInteractive}/bin/sh = icnu ${pkgs.bashInteractive}/bin/sh = icnu
${pkgs.bashInteractive}/bin/bash = icnu ${pkgs.bashInteractive}/bin/bash = icnu
${config.users.defaultUserShell} = icnu ${config.users.defaultUserShell} = icnu
''; '';
footer = "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf"; footer = "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf";
passAsFile = [ "header" ]; passAsFile = [ "header" ];
} '' }
cp $headerPath $out ''
sed '1,/\[qualifiers\]/d' $footer >> $out cp $headerPath $out
''; sed '1,/\[qualifiers\]/d' $footer >> $out
'';
boot.kernelParams = [ "apparmor=1" "security=apparmor" ]; boot.kernelParams = [
"apparmor=1"
"security=apparmor"
];
systemd.services.apparmor = { systemd.services.apparmor = {
after = [ after = [
"local-fs.target" "local-fs.target"
"systemd-journald-audit.socket" "systemd-journald-audit.socket"
]; ];
before = [ "sysinit.target" "shutdown.target" ]; before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ]; conflicts = [ "shutdown.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
unitConfig = { unitConfig = {
Description="Load AppArmor policies"; Description = "Load AppArmor policies";
DefaultDependencies = "no"; DefaultDependencies = "no";
ConditionSecurity = "apparmor"; ConditionSecurity = "apparmor";
}; };
@ -179,37 +219,48 @@ in
etc."apparmor/parser.conf".source etc."apparmor/parser.conf".source
etc."apparmor.d".source etc."apparmor.d".source
]; ];
serviceConfig = let serviceConfig =
killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" '' let
set -eu killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" ''
${pkgs.apparmor-bin-utils}/bin/aa-status --json | set -eu
${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' | ${pkgs.apparmor-bin-utils}/bin/aa-status --json |
xargs --verbose --no-run-if-empty --delimiter='\n' \ ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' |
kill xargs --verbose --no-run-if-empty --delimiter='\n' \
''; kill
commonOpts = p: "--verbose --show-cache ${optionalString (!p.enforce) "--complain "}${p.profile}"; '';
in { commonOpts = p: "--verbose --show-cache ${optionalString (!p.enforce) "--complain "}${p.profile}";
Type = "oneshot"; in
RemainAfterExit = "yes"; {
ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown"; Type = "oneshot";
ExecStart = mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies; RemainAfterExit = "yes";
ExecStartPost = optional cfg.killUnconfinedConfinables killUnconfinedConfinables; ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown";
ExecReload = ExecStart = mapAttrsToList (
# Add or replace into the kernel profiles in enabledPolicies n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}"
# (because AppArmor can do that without stopping the processes already confined). ) enabledPolicies;
mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++ ExecStartPost = optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
# Remove from the kernel any profile whose name is not ExecReload =
# one of the names within the content of the profiles in enabledPolicies # Add or replace into the kernel profiles in enabledPolicies
# (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory). # (because AppArmor can do that without stopping the processes already confined).
# Note that this does not remove profiles dynamically generated by libvirt. mapAttrsToList (
[ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++ n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}"
# Optionally kill the processes which are unconfined but now have a profile loaded ) enabledPolicies
# (because AppArmor can only start to confine new processes). ++
optional cfg.killUnconfinedConfinables killUnconfinedConfinables; # Remove from the kernel any profile whose name is not
ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown"; # one of the names within the content of the profiles in enabledPolicies
CacheDirectory = [ "apparmor" "apparmor/logprof" ]; # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory).
CacheDirectoryMode = "0700"; # Note that this does not remove profiles dynamically generated by libvirt.
}; [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ]
++
# Optionally kill the processes which are unconfined but now have a profile loaded
# (because AppArmor can only start to confine new processes).
optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
CacheDirectory = [
"apparmor"
"apparmor/logprof"
];
CacheDirectoryMode = "0700";
};
}; };
}; };

View file

@ -4,12 +4,10 @@
pkgs, pkgs,
... ...
}: }:
with lib; let with lib;
let
cfg = config.security.ipa; cfg = config.security.ipa;
pyBool = x: pyBool = x: if x then "True" else "False";
if x
then "True"
else "False";
ldapConf = pkgs.writeText "ldap.conf" '' ldapConf = pkgs.writeText "ldap.conf" ''
# Turning this off breaks GSSAPI used with krb5 when rdns = false # Turning this off breaks GSSAPI used with krb5 when rdns = false
@ -21,14 +19,16 @@ with lib; let
''; '';
nssDb = nssDb =
pkgs.runCommand "ipa-nssdb" pkgs.runCommand "ipa-nssdb"
{ {
nativeBuildInputs = [pkgs.nss.tools]; nativeBuildInputs = [ pkgs.nss.tools ];
} '' }
mkdir -p $out ''
certutil -d $out -N --empty-password mkdir -p $out
certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate} certutil -d $out -N --empty-password
''; certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate}
in { '';
in
{
options = { options = {
security.ipa = { security.ipa = {
enable = mkEnableOption "FreeIPA domain integration"; enable = mkEnableOption "FreeIPA domain integration";
@ -88,8 +88,11 @@ in {
ipaHostname = mkOption { ipaHostname = mkOption {
type = types.str; type = types.str;
example = "myworkstation.example.com"; example = "myworkstation.example.com";
default = if config.networking.domain != null then config.networking.fqdn default =
else "${config.networking.hostName}.${cfg.domain}"; if config.networking.domain != null then
config.networking.fqdn
else
"${config.networking.hostName}.${cfg.domain}";
defaultText = literalExpression '' defaultText = literalExpression ''
if config.networking.domain != null then config.networking.fqdn if config.networking.domain != null then config.networking.fqdn
else "''${networking.hostName}.''${security.ipa.domain}" else "''${networking.hostName}.''${security.ipa.domain}"
@ -99,7 +102,7 @@ in {
ifpAllowedUids = mkOption { ifpAllowedUids = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = ["root"]; default = [ "root" ];
description = "A list of users allowed to access the ifp dbus interface."; description = "A list of users allowed to access the ifp dbus interface.";
}; };
@ -138,7 +141,10 @@ in {
} }
]; ];
environment.systemPackages = with pkgs; [krb5Full freeipa]; environment.systemPackages = with pkgs; [
krb5Full
freeipa
];
environment.etc = { environment.etc = {
"ipa/default.conf".text = '' "ipa/default.conf".text = ''
@ -195,7 +201,10 @@ in {
systemd.services."ipa-activation" = { systemd.services."ipa-activation" = {
wantedBy = [ "sysinit.target" ]; wantedBy = [ "sysinit.target" ];
before = [ "sysinit.target" "shutdown.target" ]; before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ]; conflicts = [ "shutdown.target" ];
unitConfig.DefaultDependencies = false; unitConfig.DefaultDependencies = false;
serviceConfig.Type = "oneshot"; serviceConfig.Type = "oneshot";
@ -234,8 +243,7 @@ in {
cache_credentials = ${pyBool cfg.cacheCredentials} cache_credentials = ${pyBool cfg.cacheCredentials}
krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords} krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords}
${optionalString ((toLower cfg.domain) != (toLower cfg.realm)) ${optionalString ((toLower cfg.domain) != (toLower cfg.realm)) "krb5_realm = ${cfg.realm}"}
"krb5_realm = ${cfg.realm}"}
dyndns_update = ${pyBool cfg.dyndns.enable} dyndns_update = ${pyBool cfg.dyndns.enable}
dyndns_iface = ${cfg.dyndns.interface} dyndns_iface = ${cfg.dyndns.interface}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,12 @@
# A module for rtkit, a DBus system service that hands out realtime # A module for rtkit, a DBus system service that hands out realtime
# scheduling priority to processes that ask for it. # scheduling priority to processes that ask for it.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
@ -22,7 +27,6 @@ with lib;
}; };
config = mkIf config.security.rtkit.enable { config = mkIf config.security.rtkit.enable {
security.polkit.enable = true; security.polkit.enable = true;
@ -34,13 +38,12 @@ with lib;
services.dbus.packages = [ pkgs.rtkit ]; services.dbus.packages = [ pkgs.rtkit ];
users.users.rtkit = users.users.rtkit = {
{ isSystemUser = true;
isSystemUser = true; group = "rtkit";
group = "rtkit"; description = "RealtimeKit daemon";
description = "RealtimeKit daemon"; };
}; users.groups.rtkit = { };
users.groups.rtkit = {};
}; };

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (config.security) wrapperDir wrappers; inherit (config.security) wrapperDir wrappers;
@ -9,26 +14,30 @@ let
# musl is security-focused and generally more minimal, so it's a better choice here. # musl is security-focused and generally more minimal, so it's a better choice here.
# The dynamic linker is still a fairly complex piece of code, and the wrappers are # The dynamic linker is still a fairly complex piece of code, and the wrappers are
# quite small, so linking it statically is more appropriate. # quite small, so linking it statically is more appropriate.
securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix { securityWrapper =
inherit sourceProg; sourceProg:
pkgs.pkgsStatic.callPackage ./wrapper.nix {
inherit sourceProg;
# glibc definitions of insecure environment variables # glibc definitions of insecure environment variables
# #
# We extract the single header file we need into its own derivation, # We extract the single header file we need into its own derivation,
# so that we don't have to pull full glibc sources to build wrappers. # so that we don't have to pull full glibc sources to build wrappers.
# #
# They're taken from pkgs.glibc so that we don't have to keep as close # They're taken from pkgs.glibc so that we don't have to keep as close
# an eye on glibc changes. Not every relevant variable is in this header, # an eye on glibc changes. Not every relevant variable is in this header,
# so we maintain a slightly stricter list in wrapper.c itself as well. # so we maintain a slightly stricter list in wrapper.c itself as well.
unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc) unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc) (
({ name, ... }: { { name, ... }:
name = "${name}-unsecvars"; {
installPhase = '' name = "${name}-unsecvars";
mkdir $out installPhase = ''
cp sysdeps/generic/unsecvars.h $out mkdir $out
''; cp sysdeps/generic/unsecvars.h $out
}); '';
}; }
);
};
fileModeType = fileModeType =
let let
@ -37,40 +46,41 @@ let
numeric = "[-+=]?[0-7]{0,4}"; numeric = "[-+=]?[0-7]{0,4}";
mode = "((${symbolic})(,${symbolic})*)|(${numeric})"; mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
in in
lib.types.strMatching mode lib.types.strMatching mode // { description = "file mode string"; };
// { description = "file mode string"; };
wrapperType = lib.types.submodule ({ name, config, ... }: { wrapperType = lib.types.submodule (
options.source = lib.mkOption { name, config, ... }:
{ type = lib.types.path; {
options.source = lib.mkOption {
type = lib.types.path;
description = "The absolute path to the program to be wrapped."; description = "The absolute path to the program to be wrapped.";
}; };
options.program = lib.mkOption options.program = lib.mkOption {
{ type = with lib.types; nullOr str; type = with lib.types; nullOr str;
default = name; default = name;
description = '' description = ''
The name of the wrapper program. Defaults to the attribute name. The name of the wrapper program. Defaults to the attribute name.
''; '';
}; };
options.owner = lib.mkOption options.owner = lib.mkOption {
{ type = lib.types.str; type = lib.types.str;
description = "The owner of the wrapper program."; description = "The owner of the wrapper program.";
}; };
options.group = lib.mkOption options.group = lib.mkOption {
{ type = lib.types.str; type = lib.types.str;
description = "The group of the wrapper program."; description = "The group of the wrapper program.";
}; };
options.permissions = lib.mkOption options.permissions = lib.mkOption {
{ type = fileModeType; type = fileModeType;
default = "u+rx,g+x,o+x"; default = "u+rx,g+x,o+x";
example = "a+rx"; example = "a+rx";
description = '' description = ''
The permissions of the wrapper program. The format is that of a The permissions of the wrapper program. The format is that of a
symbolic or numeric file mode understood by {command}`chmod`. symbolic or numeric file mode understood by {command}`chmod`.
''; '';
}; };
options.capabilities = lib.mkOption options.capabilities = lib.mkOption {
{ type = lib.types.commas; type = lib.types.commas;
default = ""; default = "";
description = '' description = ''
A comma-separated list of capability clauses to be given to the A comma-separated list of capability clauses to be given to the
@ -89,27 +99,29 @@ let
::: :::
''; '';
}; };
options.setuid = lib.mkOption options.setuid = lib.mkOption {
{ type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
description = "Whether to add the setuid bit the wrapper program."; description = "Whether to add the setuid bit the wrapper program.";
}; };
options.setgid = lib.mkOption options.setgid = lib.mkOption {
{ type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
description = "Whether to add the setgid bit the wrapper program."; description = "Whether to add the setgid bit the wrapper program.";
}; };
}); }
);
###### Activation script for the setcap wrappers ###### Activation script for the setcap wrappers
mkSetcapProgram = mkSetcapProgram =
{ program {
, capabilities program,
, source capabilities,
, owner source,
, group owner,
, permissions group,
, ... permissions,
...
}: }:
'' ''
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
@ -129,14 +141,15 @@ let
###### Activation script for the setuid wrappers ###### Activation script for the setuid wrappers
mkSetuidProgram = mkSetuidProgram =
{ program {
, source program,
, owner source,
, group owner,
, setuid group,
, setgid setuid,
, permissions setgid,
, ... permissions,
...
}: }:
'' ''
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
@ -148,13 +161,9 @@ let
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}" chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
''; '';
mkWrappedPrograms = mkWrappedPrograms = builtins.map (
builtins.map opts: if opts.capabilities != "" then mkSetcapProgram opts else mkSetuidProgram opts
(opts: ) (lib.attrValues wrappers);
if opts.capabilities != ""
then mkSetcapProgram opts
else mkSetuidProgram opts
) (lib.attrValues wrappers);
in in
{ {
imports = [ imports = [
@ -171,35 +180,34 @@ in
security.wrappers = lib.mkOption { security.wrappers = lib.mkOption {
type = lib.types.attrsOf wrapperType; type = lib.types.attrsOf wrapperType;
default = {}; default = { };
example = lib.literalExpression example = lib.literalExpression ''
'' {
{ # a setuid root program
# a setuid root program doas =
doas = { setuid = true;
{ setuid = true; owner = "root";
owner = "root"; group = "root";
group = "root"; source = "''${pkgs.doas}/bin/doas";
source = "''${pkgs.doas}/bin/doas"; };
};
# a setgid program # a setgid program
locate = locate =
{ setgid = true; { setgid = true;
owner = "root"; owner = "root";
group = "mlocate"; group = "mlocate";
source = "''${pkgs.locate}/bin/locate"; source = "''${pkgs.locate}/bin/locate";
}; };
# a program with the CAP_NET_RAW capability # a program with the CAP_NET_RAW capability
ping = ping =
{ owner = "root"; { owner = "root";
group = "root"; group = "root";
capabilities = "cap_net_raw+ep"; capabilities = "cap_net_raw+ep";
source = "''${pkgs.iputils.out}/bin/ping"; source = "''${pkgs.iputils.out}/bin/ping";
}; };
} }
''; '';
description = '' description = ''
This option effectively allows adding setuid/setgid bits, capabilities, This option effectively allows adding setuid/setgid bits, capabilities,
changing file ownership and permissions of a program without directly changing file ownership and permissions of a program without directly
@ -220,9 +228,9 @@ in
}; };
security.wrapperDir = lib.mkOption { security.wrapperDir = lib.mkOption {
type = lib.types.path; type = lib.types.path;
default = "/run/wrappers/bin"; default = "/run/wrappers/bin";
internal = true; internal = true;
description = '' description = ''
This option defines the path to the wrapper programs. It This option defines the path to the wrapper programs. It
should not be overridden. should not be overridden.
@ -233,29 +241,28 @@ in
###### implementation ###### implementation
config = lib.mkIf config.security.enableWrappers { config = lib.mkIf config.security.enableWrappers {
assertions = lib.mapAttrsToList assertions = lib.mapAttrsToList (name: opts: {
(name: opts: assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
{ assertion = opts.setuid || opts.setgid -> opts.capabilities == ""; message = ''
message = '' The security.wrappers.${name} wrapper is not valid:
The security.wrappers.${name} wrapper is not valid: setuid/setgid and capabilities are mutually exclusive.
setuid/setgid and capabilities are mutually exclusive. '';
''; }) wrappers;
}
) wrappers;
security.wrappers = security.wrappers =
let let
mkSetuidRoot = source: mkSetuidRoot = source: {
{ setuid = true; setuid = true;
owner = "root"; owner = "root";
group = "root"; group = "root";
inherit source; inherit source;
}; };
in in
{ # These are mount related wrappers that require the +s permission. {
fusermount = mkSetuidRoot "${lib.getBin pkgs.fuse}/bin/fusermount"; # These are mount related wrappers that require the +s permission.
fusermount = mkSetuidRoot "${lib.getBin pkgs.fuse}/bin/fusermount";
fusermount3 = mkSetuidRoot "${lib.getBin pkgs.fuse3}/bin/fusermount3"; fusermount3 = mkSetuidRoot "${lib.getBin pkgs.fuse3}/bin/fusermount3";
mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount"; mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount";
umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount"; umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount";
}; };
@ -266,33 +273,45 @@ in
export PATH="${wrapperDir}:$PATH" export PATH="${wrapperDir}:$PATH"
''; '';
security.apparmor.includes = lib.mapAttrs' (wrapName: wrap: lib.nameValuePair security.apparmor.includes = lib.mapAttrs' (
"nixos/security.wrappers/${wrapName}" '' wrapName: wrap:
include "${pkgs.apparmorRulesFromClosure { name="security.wrappers.${wrapName}"; } [ lib.nameValuePair "nixos/security.wrappers/${wrapName}" ''
(securityWrapper wrap.source) include "${
]}" pkgs.apparmorRulesFromClosure { name = "security.wrappers.${wrapName}"; } [
mrpx ${wrap.source}, (securityWrapper wrap.source)
'') wrappers; ]
}"
mrpx ${wrap.source},
''
) wrappers;
systemd.mounts = [{ systemd.mounts = [
where = parentWrapperDir; {
what = "tmpfs"; where = parentWrapperDir;
type = "tmpfs"; what = "tmpfs";
options = lib.concatStringsSep "," ([ type = "tmpfs";
"nodev" options = lib.concatStringsSep "," ([
"mode=755" "nodev"
"size=${config.security.wrapperDirSize}" "mode=755"
]); "size=${config.security.wrapperDirSize}"
}]; ]);
}
];
systemd.services.suid-sgid-wrappers = { systemd.services.suid-sgid-wrappers = {
description = "Create SUID/SGID Wrappers"; description = "Create SUID/SGID Wrappers";
wantedBy = [ "sysinit.target" ]; wantedBy = [ "sysinit.target" ];
before = [ "sysinit.target" "shutdown.target" ]; before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ]; conflicts = [ "shutdown.target" ];
after = [ "systemd-sysusers.service" ]; after = [ "systemd-sysusers.service" ];
unitConfig.DefaultDependencies = false; unitConfig.DefaultDependencies = false;
unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ]; unitConfig.RequiresMountsFor = [
"/nix/store"
"/run/wrappers"
];
serviceConfig.Type = "oneshot"; serviceConfig.Type = "oneshot";
script = '' script = ''
chmod 755 "${parentWrapperDir}" chmod 755 "${parentWrapperDir}"
@ -321,17 +340,15 @@ in
}; };
###### wrappers consistency checks ###### wrappers consistency checks
system.checks = lib.singleton (pkgs.runCommandLocal system.checks = lib.singleton (
"ensure-all-wrappers-paths-exist" { } pkgs.runCommandLocal "ensure-all-wrappers-paths-exist" { } ''
''
# make sure we produce output # make sure we produce output
mkdir -p $out mkdir -p $out
echo -n "Checking that Nix store paths of all wrapped programs exist... " echo -n "Checking that Nix store paths of all wrapped programs exist... "
declare -A wrappers declare -A wrappers
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "wrappers['${n}']='${v.source}'") wrappers)}
"wrappers['${n}']='${v.source}'") wrappers)}
for name in "''${!wrappers[@]}"; do for name in "''${!wrappers[@]}"; do
path="''${wrappers[$name]}" path="''${wrappers[$name]}"
@ -346,6 +363,7 @@ in
done done
echo "OK" echo "OK"
''); ''
);
}; };
} }

View file

@ -1,9 +1,16 @@
# ALSA sound support. # ALSA sound support.
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
imports = [ imports = [
(lib.mkRemovedOptionModule [ "sound" ] "The option was heavily overloaded and can be removed from most configurations.") (lib.mkRemovedOptionModule [
"sound"
] "The option was heavily overloaded and can be removed from most configurations.")
]; ];
options.hardware.alsa.enablePersistence = lib.mkOption { options.hardware.alsa.enablePersistence = lib.mkOption {

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
@ -45,7 +50,9 @@ in
ExecStart = ExecStart =
let let
# these values are null by default but should not appear in the final config # these values are null by default but should not appear in the final config
filteredSettings = filterAttrs (n: v: !((n == "tls-cert" || n == "tls-key") && v == null)) cfg.settings; filteredSettings = filterAttrs (
n: v: !((n == "tls-cert" || n == "tls-key") && v == null)
) cfg.settings;
in in
"${pkgs.gonic}/bin/gonic -config-path ${settingsFormat.generate "gonic" filteredSettings}"; "${pkgs.gonic}/bin/gonic -config-path ${settingsFormat.generate "gonic" filteredSettings}";
DynamicUser = true; DynamicUser = true;
@ -58,17 +65,23 @@ in
BindPaths = [ BindPaths = [
cfg.settings.playlists-path cfg.settings.playlists-path
]; ];
BindReadOnlyPaths = [ BindReadOnlyPaths =
# gonic can access scrobbling services [
"-/etc/resolv.conf" # gonic can access scrobbling services
"-/etc/ssl/certs/ca-certificates.crt" "-/etc/resolv.conf"
builtins.storeDir "-/etc/ssl/certs/ca-certificates.crt"
cfg.settings.podcast-path builtins.storeDir
] ++ cfg.settings.music-path cfg.settings.podcast-path
++ lib.optional (cfg.settings.tls-cert != null) cfg.settings.tls-cert ]
++ lib.optional (cfg.settings.tls-key != null) cfg.settings.tls-key; ++ cfg.settings.music-path
++ lib.optional (cfg.settings.tls-cert != null) cfg.settings.tls-cert
++ lib.optional (cfg.settings.tls-key != null) cfg.settings.tls-key;
CapabilityBoundingSet = ""; CapabilityBoundingSet = "";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true; RestrictNamespaces = true;
PrivateDevices = true; PrivateDevices = true;
PrivateUsers = true; PrivateUsers = true;
@ -79,7 +92,10 @@ in
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelTunables = true; ProtectKernelTunables = true;
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ]; SystemCallFilter = [
"@system-service"
"~@privileged"
];
RestrictRealtime = true; RestrictRealtime = true;
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
name = "mpd"; name = "mpd";
@ -7,13 +12,17 @@ let
gid = config.ids.gids.mpd; gid = config.ids.gids.mpd;
cfg = config.services.mpd; cfg = config.services.mpd;
credentialsPlaceholder = (creds: credentialsPlaceholder = (
creds:
let let
placeholders = (lib.imap0 placeholders = (
(i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"'') lib.imap0 (
creds); i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"''
) creds
);
in in
lib.concatStringsSep "\n" placeholders); lib.concatStringsSep "\n" placeholders
);
mpdConf = pkgs.writeText "mpd.conf" '' mpdConf = pkgs.writeText "mpd.conf" ''
# This file was automatically generated by NixOS. Edit mpd's configuration # This file was automatically generated by NixOS. Edit mpd's configuration
@ -28,8 +37,10 @@ let
state_file "${cfg.dataDir}/state" state_file "${cfg.dataDir}/state"
sticker_file "${cfg.dataDir}/sticker.sql" sticker_file "${cfg.dataDir}/sticker.sql"
${lib.optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''} ${lib.optionalString (
${lib.optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''} cfg.network.listenAddress != "any"
) ''bind_to_address "${cfg.network.listenAddress}"''}
${lib.optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
${lib.optionalString (cfg.fluidsynth) '' ${lib.optionalString (cfg.fluidsynth) ''
decoder { decoder {
plugin "fluidsynth" plugin "fluidsynth"
@ -37,12 +48,13 @@ let
} }
''} ''}
${lib.optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)} ${lib.optionalString (cfg.credentials != [ ]) (credentialsPlaceholder cfg.credentials)}
${cfg.extraConfig} ${cfg.extraConfig}
''; '';
in { in
{
###### interface ###### interface
@ -160,33 +172,53 @@ in {
}; };
credentials = lib.mkOption { credentials = lib.mkOption {
type = lib.types.listOf (lib.types.submodule { type = lib.types.listOf (
options = { lib.types.submodule {
passwordFile = lib.mkOption { options = {
type = lib.types.path; passwordFile = lib.mkOption {
description = '' type = lib.types.path;
Path to file containing the password. description = ''
''; Path to file containing the password.
'';
};
permissions =
let
perms = [
"read"
"add"
"control"
"admin"
];
in
lib.mkOption {
type = lib.types.listOf (lib.types.enum perms);
default = [ "read" ];
description = ''
List of permissions that are granted with this password.
Permissions can be "${lib.concatStringsSep "\", \"" perms}".
'';
};
}; };
permissions = let }
perms = ["read" "add" "control" "admin"]; );
in lib.mkOption {
type = lib.types.listOf (lib.types.enum perms);
default = [ "read" ];
description = ''
List of permissions that are granted with this password.
Permissions can be "${lib.concatStringsSep "\", \"" perms}".
'';
};
};
});
description = '' description = ''
Credentials and permissions for accessing the mpd server. Credentials and permissions for accessing the mpd server.
''; '';
default = []; default = [ ];
example = [ example = [
{passwordFile = "/var/lib/secrets/mpd_readonly_password"; permissions = [ "read" ];} {
{passwordFile = "/var/lib/secrets/mpd_admin_password"; permissions = ["read" "add" "control" "admin"];} passwordFile = "/var/lib/secrets/mpd_readonly_password";
permissions = [ "read" ];
}
{
passwordFile = "/var/lib/secrets/mpd_admin_password";
permissions = [
"read"
"add"
"control"
"admin"
];
}
]; ];
}; };
@ -201,7 +233,6 @@ in {
}; };
###### implementation ###### implementation
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
@ -212,10 +243,15 @@ in {
systemd.sockets.mpd = lib.mkIf cfg.startWhenNeeded { systemd.sockets.mpd = lib.mkIf cfg.startWhenNeeded {
wantedBy = [ "sockets.target" ]; wantedBy = [ "sockets.target" ];
listenStreams = [ listenStreams = [
"" # Note: this is needed to override the upstream unit "" # Note: this is needed to override the upstream unit
(if pkgs.lib.hasPrefix "/" cfg.network.listenAddress (
then cfg.network.listenAddress if pkgs.lib.hasPrefix "/" cfg.network.listenAddress then
else "${lib.optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}") cfg.network.listenAddress
else
"${
lib.optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"
}${toString cfg.network.port}"
)
]; ];
}; };
@ -226,23 +262,36 @@ in {
'' ''
set -euo pipefail set -euo pipefail
install -m 600 ${mpdConf} /run/mpd/mpd.conf install -m 600 ${mpdConf} /run/mpd/mpd.conf
'' + lib.optionalString (cfg.credentials != []) ''
(lib.concatStringsSep "\n" + lib.optionalString (cfg.credentials != [ ]) (
(lib.imap0 lib.concatStringsSep "\n" (
(i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'') lib.imap0 (
cfg.credentials)); i: c:
''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf''
) cfg.credentials
)
);
serviceConfig = serviceConfig = {
{ User = "${cfg.user}";
User = "${cfg.user}"; # Note: the first "" overrides the ExecStart from the upstream unit
# Note: the first "" overrides the ExecStart from the upstream unit ExecStart = [
ExecStart = [ "" "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" ]; ""
RuntimeDirectory = "mpd"; "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf"
StateDirectory = [] ];
++ lib.optionals (cfg.dataDir == "/var/lib/${name}") [ name ] RuntimeDirectory = "mpd";
++ lib.optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [ name "${name}/playlists" ] StateDirectory =
++ lib.optionals (cfg.musicDirectory == "/var/lib/${name}/music") [ name "${name}/music" ]; [ ]
}; ++ lib.optionals (cfg.dataDir == "/var/lib/${name}") [ name ]
++ lib.optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [
name
"${name}/playlists"
]
++ lib.optionals (cfg.musicDirectory == "/var/lib/${name}/music") [
name
"${name}/music"
];
};
}; };
users.users = lib.optionalAttrs (cfg.user == name) { users.users = lib.optionalAttrs (cfg.user == name) {

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,23 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.services.sanoid; cfg = config.services.sanoid;
datasetSettingsType = with lib.types; datasetSettingsType =
(attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // { with lib.types;
(attrsOf (
nullOr (oneOf [
str
int
bool
(listOf str)
])
))
// {
description = "dataset/template options"; description = "dataset/template options";
}; };
@ -48,10 +62,13 @@ let
datasetOptions = rec { datasetOptions = rec {
use_template = lib.mkOption { use_template = lib.mkOption {
description = "Names of the templates to use for this dataset."; description = "Names of the templates to use for this dataset.";
type = lib.types.listOf (lib.types.str // { type = lib.types.listOf (
check = (lib.types.enum (lib.attrNames cfg.templates)).check; lib.types.str
description = "configured template name"; // {
}); check = (lib.types.enum (lib.attrNames cfg.templates)).check;
description = "configured template name";
}
);
default = [ ]; default = [ ];
}; };
useTemplate = use_template; useTemplate = use_template;
@ -63,7 +80,12 @@ let
recursively in an atomic way without the possibility to recursively in an atomic way without the possibility to
override settings for child datasets. override settings for child datasets.
''; '';
type = with lib.types; oneOf [ bool (enum [ "zfs" ]) ]; type =
with lib.types;
oneOf [
bool
(enum [ "zfs" ])
];
default = false; default = false;
}; };
@ -80,26 +102,32 @@ let
# Function to build "zfs allow" and "zfs unallow" commands for the # Function to build "zfs allow" and "zfs unallow" commands for the
# filesystems we've delegated permissions to. # filesystems we've delegated permissions to.
buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [ buildAllowCommand =
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS zfsAction: permissions: dataset:
"-+/run/booted-system/sw/bin/zfs" lib.escapeShellArgs [
zfsAction # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
"sanoid" "-+/run/booted-system/sw/bin/zfs"
(lib.concatStringsSep "," permissions) zfsAction
dataset "sanoid"
]; (lib.concatStringsSep "," permissions)
dataset
];
configFile = configFile =
let let
mkValueString = v: mkValueString =
if lib.isList v then lib.concatStringsSep "," v v: if lib.isList v then lib.concatStringsSep "," v else lib.generators.mkValueStringDefault { } v;
else lib.generators.mkValueStringDefault { } v;
mkKeyValue = k: v: mkKeyValue =
if v == null then "" k: v:
else if k == "processChildrenOnly" then "" if v == null then
else if k == "useTemplate" then "" ""
else lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v; else if k == "processChildrenOnly" then
""
else if k == "useTemplate" then
""
else
lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
in in
lib.generators.toINI { inherit mkKeyValue; } cfg.settings; lib.generators.toINI { inherit mkKeyValue; } cfg.settings;
@ -111,7 +139,7 @@ in
options.services.sanoid = { options.services.sanoid = {
enable = lib.mkEnableOption "Sanoid ZFS snapshotting service"; enable = lib.mkEnableOption "Sanoid ZFS snapshotting service";
package = lib.mkPackageOption pkgs "sanoid" {}; package = lib.mkPackageOption pkgs "sanoid" { };
interval = lib.mkOption { interval = lib.mkOption {
type = lib.types.str; type = lib.types.str;
@ -126,21 +154,32 @@ in
}; };
datasets = lib.mkOption { datasets = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: { type = lib.types.attrsOf (
freeformType = datasetSettingsType; lib.types.submodule (
options = commonOptions // datasetOptions; { config, options, ... }:
config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (options.useTemplate or { }); {
config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (options.processChildrenOnly or { }); freeformType = datasetSettingsType;
})); options = commonOptions // datasetOptions;
config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
options.useTemplate or { }
);
config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
options.processChildrenOnly or { }
);
}
)
);
default = { }; default = { };
description = "Datasets to snapshot."; description = "Datasets to snapshot.";
}; };
templates = lib.mkOption { templates = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule { type = lib.types.attrsOf (
freeformType = datasetSettingsType; lib.types.submodule {
options = commonOptions; freeformType = datasetSettingsType;
}); options = commonOptions;
}
);
default = { }; default = { };
description = "Templates for datasets."; description = "Templates for datasets.";
}; };
@ -157,7 +196,11 @@ in
extraArgs = lib.mkOption { extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
example = [ "--verbose" "--readonly" "--debug" ]; example = [
"--verbose"
"--readonly"
"--debug"
];
description = '' description = ''
Extra arguments to pass to sanoid. See Extra arguments to pass to sanoid. See
<https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options> <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
@ -177,14 +220,29 @@ in
systemd.services.sanoid = { systemd.services.sanoid = {
description = "Sanoid snapshot service"; description = "Sanoid snapshot service";
serviceConfig = { serviceConfig = {
ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets); ExecStartPre = (
ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets); map (buildAllowCommand "allow" [
ExecStart = lib.escapeShellArgs ([ "snapshot"
"${cfg.package}/bin/sanoid" "mount"
"--cron" "destroy"
"--configdir" ]) datasets
(pkgs.writeTextDir "sanoid.conf" configFile) );
] ++ cfg.extraArgs); ExecStopPost = (
map (buildAllowCommand "unallow" [
"snapshot"
"mount"
"destroy"
]) datasets
);
ExecStart = lib.escapeShellArgs (
[
"${cfg.package}/bin/sanoid"
"--cron"
"--configdir"
(pkgs.writeTextDir "sanoid.conf" configFile)
]
++ cfg.extraArgs
);
User = "sanoid"; User = "sanoid";
Group = "sanoid"; Group = "sanoid";
DynamicUser = true; DynamicUser = true;

View file

@ -1,54 +1,69 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.services.syncoid; cfg = config.services.syncoid;
# Extract local dasaset names (so no datasets containing "@") # Extract local dasaset names (so no datasets containing "@")
localDatasetName = d: lib.optionals (d != null) ( localDatasetName =
let m = builtins.match "([^/@]+[^@]*)" d; in d:
lib.optionals (m != null) m lib.optionals (d != null) (
); let
m = builtins.match "([^/@]+[^@]*)" d;
in
lib.optionals (m != null) m
);
# Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
escapeUnitName = name: escapeUnitName =
lib.concatMapStrings (s: if lib.isList s then "-" else s) name:
(builtins.split "[^a-zA-Z0-9_.\\-]+" name); lib.concatMapStrings (s: if lib.isList s then "-" else s) (
builtins.split "[^a-zA-Z0-9_.\\-]+" name
);
# Function to build "zfs allow" commands for the filesystems we've delegated # Function to build "zfs allow" commands for the filesystems we've delegated
# permissions to. It also checks if the target dataset exists before # permissions to. It also checks if the target dataset exists before
# delegating permissions, if it doesn't exist we delegate it to the parent # delegating permissions, if it doesn't exist we delegate it to the parent
# dataset (if it exists). This should solve the case of provisoning new # dataset (if it exists). This should solve the case of provisoning new
# datasets. # datasets.
buildAllowCommand = permissions: dataset: ( buildAllowCommand =
"-+${pkgs.writeShellScript "zfs-allow-${dataset}" '' permissions: dataset:
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS (
"-+${pkgs.writeShellScript "zfs-allow-${dataset}" ''
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
# Run a ZFS list on the dataset to check if it exists # Run a ZFS list on the dataset to check if it exists
if ${lib.escapeShellArgs [ if ${
"/run/booted-system/sw/bin/zfs" lib.escapeShellArgs [
"list" "/run/booted-system/sw/bin/zfs"
dataset "list"
]} 2> /dev/null; then dataset
${lib.escapeShellArgs [ ]
"/run/booted-system/sw/bin/zfs" } 2> /dev/null; then
"allow"
cfg.user
(lib.concatStringsSep "," permissions)
dataset
]}
${lib.optionalString ((builtins.dirOf dataset) != ".") ''
else
${lib.escapeShellArgs [ ${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs" "/run/booted-system/sw/bin/zfs"
"allow" "allow"
cfg.user cfg.user
(lib.concatStringsSep "," permissions) (lib.concatStringsSep "," permissions)
# Remove the last part of the path dataset
(builtins.dirOf dataset)
]} ]}
''} ${lib.optionalString ((builtins.dirOf dataset) != ".") ''
fi else
''}" ${lib.escapeShellArgs [
); "/run/booted-system/sw/bin/zfs"
"allow"
cfg.user
(lib.concatStringsSep "," permissions)
# Remove the last part of the path
(builtins.dirOf dataset)
]}
''}
fi
''}"
);
# Function to build "zfs unallow" commands for the filesystems we've # Function to build "zfs unallow" commands for the filesystems we've
# delegated permissions to. Here we unallow both the target but also # delegated permissions to. Here we unallow both the target but also
@ -56,26 +71,30 @@ let
# knowing if the allow command did execute on the parent dataset or # knowing if the allow command did execute on the parent dataset or
# not in the pre-hook. We can't run the same if in the post hook # not in the pre-hook. We can't run the same if in the post hook
# since the dataset should have been created at this point. # since the dataset should have been created at this point.
buildUnallowCommand = permissions: dataset: ( buildUnallowCommand =
"-+${pkgs.writeShellScript "zfs-unallow-${dataset}" '' permissions: dataset:
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS (
${lib.escapeShellArgs [ "-+${pkgs.writeShellScript "zfs-unallow-${dataset}" ''
"/run/booted-system/sw/bin/zfs" # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
"unallow" ${lib.escapeShellArgs [
cfg.user "/run/booted-system/sw/bin/zfs"
(lib.concatStringsSep "," permissions) "unallow"
dataset cfg.user
]} (lib.concatStringsSep "," permissions)
${lib.optionalString ((builtins.dirOf dataset) != ".") (lib.escapeShellArgs [ dataset
"/run/booted-system/sw/bin/zfs" ]}
"unallow" ${lib.optionalString ((builtins.dirOf dataset) != ".") (
cfg.user lib.escapeShellArgs [
(lib.concatStringsSep "," permissions) "/run/booted-system/sw/bin/zfs"
# Remove the last part of the path "unallow"
(builtins.dirOf dataset) cfg.user
])} (lib.concatStringsSep "," permissions)
''}" # Remove the last part of the path
); (builtins.dirOf dataset)
]
)}
''}"
);
in in
{ {
@ -84,7 +103,7 @@ in
options.services.syncoid = { options.services.syncoid = {
enable = lib.mkEnableOption "Syncoid ZFS synchronization service"; enable = lib.mkEnableOption "Syncoid ZFS synchronization service";
package = lib.mkPackageOption pkgs "sanoid" {}; package = lib.mkPackageOption pkgs "sanoid" { };
interval = lib.mkOption { interval = lib.mkOption {
type = lib.types.str; type = lib.types.str;
@ -131,7 +150,14 @@ in
localSourceAllow = lib.mkOption { localSourceAllow = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
# Permissions snapshot and destroy are in case --no-sync-snap is not used # Permissions snapshot and destroy are in case --no-sync-snap is not used
default = [ "bookmark" "hold" "send" "snapshot" "destroy" "mount" ]; default = [
"bookmark"
"hold"
"send"
"snapshot"
"destroy"
"mount"
];
description = '' description = ''
Permissions granted for the {option}`services.syncoid.user` user Permissions granted for the {option}`services.syncoid.user` user
for local source datasets. See for local source datasets. See
@ -142,8 +168,21 @@ in
localTargetAllow = lib.mkOption { localTargetAllow = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ "change-key" "compression" "create" "mount" "mountpoint" "receive" "rollback" ]; default = [
example = [ "create" "mount" "receive" "rollback" ]; "change-key"
"compression"
"create"
"mount"
"mountpoint"
"receive"
"rollback"
];
example = [
"create"
"mount"
"receive"
"rollback"
];
description = '' description = ''
Permissions granted for the {option}`services.syncoid.user` user Permissions granted for the {option}`services.syncoid.user` user
for local target datasets. See for local target datasets. See
@ -176,111 +215,116 @@ in
}; };
commands = lib.mkOption { commands = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { type = lib.types.attrsOf (
options = { lib.types.submodule (
source = lib.mkOption { { name, ... }:
type = lib.types.str; {
example = "pool/dataset"; options = {
description = '' source = lib.mkOption {
Source ZFS dataset. Can be either local or remote. Defaults to type = lib.types.str;
the attribute name. example = "pool/dataset";
''; description = ''
}; Source ZFS dataset. Can be either local or remote. Defaults to
the attribute name.
'';
};
target = lib.mkOption { target = lib.mkOption {
type = lib.types.str; type = lib.types.str;
example = "user@server:pool/dataset"; example = "user@server:pool/dataset";
description = '' description = ''
Target ZFS dataset. Can be either local Target ZFS dataset. Can be either local
(«pool/dataset») or remote («pool/dataset») or remote
(«user@server:pool/dataset»). («user@server:pool/dataset»).
''; '';
}; };
recursive = lib.mkEnableOption ''the transfer of child datasets''; recursive = lib.mkEnableOption ''the transfer of child datasets'';
sshKey = lib.mkOption { sshKey = lib.mkOption {
type = with lib.types; nullOr (coercedTo path toString str); type = with lib.types; nullOr (coercedTo path toString str);
description = '' description = ''
SSH private key file to use to login to the remote system. SSH private key file to use to login to the remote system.
Defaults to {option}`services.syncoid.sshKey` option. Defaults to {option}`services.syncoid.sshKey` option.
''; '';
}; };
localSourceAllow = lib.mkOption { localSourceAllow = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
description = '' description = ''
Permissions granted for the {option}`services.syncoid.user` user Permissions granted for the {option}`services.syncoid.user` user
for local source datasets. See for local source datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html> <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions. for available permissions.
Defaults to {option}`services.syncoid.localSourceAllow` option. Defaults to {option}`services.syncoid.localSourceAllow` option.
''; '';
}; };
localTargetAllow = lib.mkOption { localTargetAllow = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
description = '' description = ''
Permissions granted for the {option}`services.syncoid.user` user Permissions granted for the {option}`services.syncoid.user` user
for local target datasets. See for local target datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html> <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions. for available permissions.
Make sure to include the `change-key` permission if you send raw encrypted datasets, Make sure to include the `change-key` permission if you send raw encrypted datasets,
the `compression` permission if you send raw compressed datasets, and so on. the `compression` permission if you send raw compressed datasets, and so on.
For remote target datasets you'll have to set your remote user permissions by yourself. For remote target datasets you'll have to set your remote user permissions by yourself.
''; '';
}; };
sendOptions = lib.mkOption { sendOptions = lib.mkOption {
type = lib.types.separatedString " "; type = lib.types.separatedString " ";
default = ""; default = "";
example = "Lc e"; example = "Lc e";
description = '' description = ''
Advanced options to pass to zfs send. Options are specified Advanced options to pass to zfs send. Options are specified
without their leading dashes and separated by spaces. without their leading dashes and separated by spaces.
''; '';
}; };
recvOptions = lib.mkOption { recvOptions = lib.mkOption {
type = lib.types.separatedString " "; type = lib.types.separatedString " ";
default = ""; default = "";
example = "ux recordsize o compression=lz4"; example = "ux recordsize o compression=lz4";
description = '' description = ''
Advanced options to pass to zfs recv. Options are specified Advanced options to pass to zfs recv. Options are specified
without their leading dashes and separated by spaces. without their leading dashes and separated by spaces.
''; '';
}; };
useCommonArgs = lib.mkOption { useCommonArgs = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = '' description = ''
Whether to add the configured common arguments to this command. Whether to add the configured common arguments to this command.
''; '';
}; };
service = lib.mkOption { service = lib.mkOption {
type = lib.types.attrs; type = lib.types.attrs;
default = { }; default = { };
description = '' description = ''
Systemd configuration specific to this syncoid service. Systemd configuration specific to this syncoid service.
''; '';
}; };
extraArgs = lib.mkOption { extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
example = [ "--sshport 2222" ]; example = [ "--sshport 2222" ];
description = "Extra syncoid arguments for this command."; description = "Extra syncoid arguments for this command.";
}; };
}; };
config = { config = {
source = lib.mkDefault name; source = lib.mkDefault name;
sshKey = lib.mkDefault cfg.sshKey; sshKey = lib.mkDefault cfg.sshKey;
localSourceAllow = lib.mkDefault cfg.localSourceAllow; localSourceAllow = lib.mkDefault cfg.localSourceAllow;
localTargetAllow = lib.mkDefault cfg.localTargetAllow; localTargetAllow = lib.mkDefault cfg.localTargetAllow;
}; };
})); }
)
);
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
@ -310,9 +354,10 @@ in
}; };
}; };
systemd.services = lib.mapAttrs' systemd.services = lib.mapAttrs' (
(name: c: name: c:
lib.nameValuePair "syncoid-${escapeUnitName name}" (lib.mkMerge [ lib.nameValuePair "syncoid-${escapeUnitName name}" (
lib.mkMerge [
{ {
description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}"; description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
after = [ "zfs.target" ]; after = [ "zfs.target" ];
@ -321,25 +366,30 @@ in
path = [ "/run/booted-system/sw/bin/" ]; path = [ "/run/booted-system/sw/bin/" ];
serviceConfig = { serviceConfig = {
ExecStartPre = ExecStartPre =
(map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source)) ++ (map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source))
(map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target)); ++ (map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target));
ExecStopPost = ExecStopPost =
(map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source)) ++ (map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source))
(map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target)); ++ (map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
ExecStart = lib.escapeShellArgs ([ "${cfg.package}/bin/syncoid" ] ExecStart = lib.escapeShellArgs (
[ "${cfg.package}/bin/syncoid" ]
++ lib.optionals c.useCommonArgs cfg.commonArgs ++ lib.optionals c.useCommonArgs cfg.commonArgs
++ lib.optional c.recursive "-r" ++ lib.optional c.recursive "-r"
++ lib.optionals (c.sshKey != null) [ "--sshkey" c.sshKey ] ++ lib.optionals (c.sshKey != null) [
"--sshkey"
c.sshKey
]
++ c.extraArgs ++ c.extraArgs
++ [ ++ [
"--sendoptions" "--sendoptions"
c.sendOptions c.sendOptions
"--recvoptions" "--recvoptions"
c.recvOptions c.recvOptions
"--no-privilege-elevation" "--no-privilege-elevation"
c.source c.source
c.target c.target
]); ]
);
User = cfg.user; User = cfg.user;
Group = cfg.group; Group = cfg.group;
StateDirectory = [ "syncoid" ]; StateDirectory = [ "syncoid" ];
@ -372,14 +422,23 @@ in
ProtectKernelTunables = true; ProtectKernelTunables = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
RemoveIPC = true; RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
RestrictSUIDSGID = true; RestrictSUIDSGID = true;
RootDirectory = "/run/syncoid/${escapeUnitName name}"; RootDirectory = "/run/syncoid/${escapeUnitName name}";
RootDirectoryStartOnly = true; RootDirectoryStartOnly = true;
BindPaths = [ "/dev/zfs" ]; BindPaths = [ "/dev/zfs" ];
BindReadOnlyPaths = [ builtins.storeDir "/etc" "/run" "/bin/sh" ]; BindReadOnlyPaths = [
builtins.storeDir
"/etc"
"/run"
"/bin/sh"
];
# Avoid useless mounting of RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace. # Avoid useless mounting of RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace.
InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ]; InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ];
MountAPIVFS = true; MountAPIVFS = true;
@ -409,9 +468,13 @@ in
} }
cfg.service cfg.service
c.service c.service
])) ]
cfg.commands; )
) cfg.commands;
}; };
meta.maintainers = with lib.maintainers; [ julm lopsided98 ]; meta.maintainers = with lib.maintainers; [
julm
lopsided98
];
} }

View file

@ -1,4 +1,10 @@
{ config, options, pkgs, lib, ... }: {
config,
options,
pkgs,
lib,
...
}:
let let
version = "1.10.1"; version = "1.10.1";
cfg = config.services.kubernetes.addons.dns; cfg = config.services.kubernetes.addons.dns;
@ -7,7 +13,8 @@ let
health = 10054; health = 10054;
metrics = 10055; metrics = 10055;
}; };
in { in
{
options.services.kubernetes.addons.dns = { options.services.kubernetes.addons.dns = {
enable = lib.mkEnableOption "kubernetes dns addon"; enable = lib.mkEnableOption "kubernetes dns addon";
@ -15,11 +22,11 @@ in {
description = "Dns addon clusterIP"; description = "Dns addon clusterIP";
# this default is also what kubernetes users # this default is also what kubernetes users
default = ( default =
lib.concatStringsSep "." ( (lib.concatStringsSep "." (
lib.take 3 (lib.splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange lib.take 3 (lib.splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange)
)) ))
) + ".254"; + ".254";
defaultText = lib.literalMD '' defaultText = lib.literalMD ''
The `x.y.z.254` IP of The `x.y.z.254` IP of
`config.${options.services.kubernetes.apiserver.serviceClusterIpRange}`. `config.${options.services.kubernetes.apiserver.serviceClusterIpRange}`.
@ -48,7 +55,10 @@ in {
See: <https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md>. See: <https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md>.
''; '';
default = "Reconcile"; default = "Reconcile";
type = lib.types.enum [ "Reconcile" "EnsureExists" ]; type = lib.types.enum [
"Reconcile"
"EnsureExists"
];
}; };
coredns = lib.mkOption { coredns = lib.mkOption {
@ -106,8 +116,9 @@ in {
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.kubernetes.kubelet.seedDockerImages = services.kubernetes.kubelet.seedDockerImages = lib.singleton (
lib.singleton (pkgs.dockerTools.pullImage cfg.coredns); pkgs.dockerTools.pullImage cfg.coredns
);
services.kubernetes.addonManager.bootstrapAddons = { services.kubernetes.addonManager.bootstrapAddons = {
coredns-cr = { coredns-cr = {
@ -125,8 +136,16 @@ in {
rules = [ rules = [
{ {
apiGroups = [ "" ]; apiGroups = [ "" ];
resources = [ "endpoints" "services" "pods" "namespaces" ]; resources = [
verbs = [ "list" "watch" ]; "endpoints"
"services"
"pods"
"namespaces"
];
verbs = [
"list"
"watch"
];
} }
{ {
apiGroups = [ "" ]; apiGroups = [ "" ];
@ -136,7 +155,10 @@ in {
{ {
apiGroups = [ "discovery.k8s.io" ]; apiGroups = [ "discovery.k8s.io" ];
resources = [ "endpointslices" ]; resources = [ "endpointslices" ];
verbs = [ "list" "watch" ]; verbs = [
"list"
"watch"
];
} }
]; ];
}; };
@ -219,10 +241,14 @@ in {
spec = { spec = {
replicas = cfg.replicas; replicas = cfg.replicas;
selector = { selector = {
matchLabels = { k8s-app = "kube-dns"; }; matchLabels = {
k8s-app = "kube-dns";
};
}; };
strategy = { strategy = {
rollingUpdate = { maxUnavailable = 1; }; rollingUpdate = {
maxUnavailable = 1;
};
type = "RollingUpdate"; type = "RollingUpdate";
}; };
template = { template = {
@ -234,7 +260,10 @@ in {
spec = { spec = {
containers = [ containers = [
{ {
args = [ "-conf" "/etc/coredns/Corefile" ]; args = [
"-conf"
"/etc/coredns/Corefile"
];
image = with cfg.coredns; "${imageName}:${finalImageTag}"; image = with cfg.coredns; "${imageName}:${finalImageTag}";
imagePullPolicy = "Never"; imagePullPolicy = "Never";
livenessProbe = { livenessProbe = {
@ -358,7 +387,9 @@ in {
protocol = "TCP"; protocol = "TCP";
} }
]; ];
selector = { k8s-app = "kube-dns"; }; selector = {
k8s-app = "kube-dns";
};
}; };
}; };
}; };

View file

@ -1,4 +1,10 @@
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
with lib; with lib;
@ -9,22 +15,40 @@ let
isRBACEnabled = elem "RBAC" cfg.authorizationMode; isRBACEnabled = elem "RBAC" cfg.authorizationMode;
apiserverServiceIP = (concatStringsSep "." ( apiserverServiceIP = (
take 3 (splitString "." cfg.serviceClusterIpRange concatStringsSep "." (take 3 (splitString "." cfg.serviceClusterIpRange)) + ".1"
)) + ".1"); );
in in
{ {
imports = [ imports = [
(mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ]) (mkRenamedOptionModule
(mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"]) [ "services" "kubernetes" "apiserver" "admissionControl" ]
[ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ]
)
(mkRenamedOptionModule
[ "services" "kubernetes" "apiserver" "address" ]
[ "services" "kubernetes" "apiserver" "bindAddress" ]
)
(mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecureBindAddress" ] "") (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecureBindAddress" ] "")
(mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecurePort" ] "") (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecurePort" ] "")
(mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "") (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
(mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ]) (mkRenamedOptionModule
(mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ]) [ "services" "kubernetes" "etcd" "servers" ]
(mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ]) [ "services" "kubernetes" "apiserver" "etcd" "servers" ]
(mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ]) )
(mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "keyFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "keyFile" ]
)
(mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "certFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "certFile" ]
)
(mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "caFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "caFile" ]
)
]; ];
###### interface ###### interface
@ -51,8 +75,18 @@ in
Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See
<https://kubernetes.io/docs/reference/access-authn-authz/authorization/> <https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
''; '';
default = ["RBAC" "Node"]; # Enabling RBAC by default, although kubernetes default is AllowAllow default = [
type = listOf (enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "Webhook" "RBAC" "Node"]); "RBAC"
"Node"
]; # Enabling RBAC by default, although kubernetes default is AllowAllow
type = listOf (enum [
"AlwaysAllow"
"AlwaysDeny"
"ABAC"
"Webhook"
"RBAC"
"Node"
]);
}; };
authorizationPolicy = mkOption { authorizationPolicy = mkOption {
@ -60,7 +94,7 @@ in
Kubernetes apiserver authorization policy file. See Kubernetes apiserver authorization policy file. See
<https://kubernetes.io/docs/reference/access-authn-authz/authorization/> <https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
''; '';
default = []; default = [ ];
type = listOf attrs; type = listOf attrs;
}; };
@ -95,7 +129,7 @@ in
Kubernetes admission control plugins to disable. See Kubernetes admission control plugins to disable. See
<https://kubernetes.io/docs/admin/admission-controllers/> <https://kubernetes.io/docs/admin/admission-controllers/>
''; '';
default = []; default = [ ];
type = listOf str; type = listOf str;
}; };
@ -107,14 +141,24 @@ in
<https://kubernetes.io/docs/admin/admission-controllers/> <https://kubernetes.io/docs/admin/admission-controllers/>
''; '';
default = [ default = [
"NamespaceLifecycle" "LimitRanger" "ServiceAccount" "NamespaceLifecycle"
"ResourceQuota" "DefaultStorageClass" "DefaultTolerationSeconds" "LimitRanger"
"ServiceAccount"
"ResourceQuota"
"DefaultStorageClass"
"DefaultTolerationSeconds"
"NodeRestriction" "NodeRestriction"
]; ];
example = [ example = [
"NamespaceLifecycle" "NamespaceExists" "LimitRanger" "NamespaceLifecycle"
"SecurityContextDeny" "ServiceAccount" "ResourceQuota" "NamespaceExists"
"PodSecurityPolicy" "NodeRestriction" "DefaultStorageClass" "LimitRanger"
"SecurityContextDeny"
"ServiceAccount"
"ResourceQuota"
"PodSecurityPolicy"
"NodeRestriction"
"DefaultStorageClass"
]; ];
type = listOf str; type = listOf str;
}; };
@ -122,7 +166,7 @@ in
etcd = { etcd = {
servers = mkOption { servers = mkOption {
description = "List of etcd servers."; description = "List of etcd servers.";
default = ["http://127.0.0.1:2379"]; default = [ "http://127.0.0.1:2379" ];
type = types.listOf types.str; type = types.listOf types.str;
}; };
@ -154,7 +198,7 @@ in
extraSANs = mkOption { extraSANs = mkOption {
description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert."; description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
default = []; default = [ ];
type = listOf str; type = listOf str;
}; };
@ -217,7 +261,10 @@ in
Kubernetes apiserver storage backend. Kubernetes apiserver storage backend.
''; '';
default = "etcd3"; default = "etcd3";
type = enum ["etcd2" "etcd3"]; type = enum [
"etcd2"
"etcd3"
];
}; };
securePort = mkOption { securePort = mkOption {
@ -312,135 +359,139 @@ in
}; };
###### implementation ###### implementation
config = mkMerge [ config = mkMerge [
(mkIf cfg.enable { (mkIf cfg.enable {
systemd.services.kube-apiserver = { systemd.services.kube-apiserver = {
description = "Kubernetes APIServer Service"; description = "Kubernetes APIServer Service";
wantedBy = [ "kubernetes.target" ]; wantedBy = [ "kubernetes.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
serviceConfig = { serviceConfig = {
Slice = "kubernetes.slice"; Slice = "kubernetes.slice";
ExecStart = '' ExecStart = ''
${top.package}/bin/kube-apiserver \ ${top.package}/bin/kube-apiserver \
--allow-privileged=${boolToString cfg.allowPrivileged} \ --allow-privileged=${boolToString cfg.allowPrivileged} \
--authorization-mode=${concatStringsSep "," cfg.authorizationMode} \ --authorization-mode=${concatStringsSep "," cfg.authorizationMode} \
${optionalString (elem "ABAC" cfg.authorizationMode) ${optionalString (elem "ABAC" cfg.authorizationMode) "--authorization-policy-file=${pkgs.writeText "kube-auth-policy.jsonl" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy)}"} \
"--authorization-policy-file=${ ${optionalString (elem "Webhook" cfg.authorizationMode) "--authorization-webhook-config-file=${cfg.webhookConfig}"} \
pkgs.writeText "kube-auth-policy.jsonl" --bind-address=${cfg.bindAddress} \
(concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy) ${optionalString (cfg.advertiseAddress != null) "--advertise-address=${cfg.advertiseAddress}"} \
}" ${optionalString (cfg.clientCaFile != null) "--client-ca-file=${cfg.clientCaFile}"} \
} \ --disable-admission-plugins=${concatStringsSep "," cfg.disableAdmissionPlugins} \
${optionalString (elem "Webhook" cfg.authorizationMode) --enable-admission-plugins=${concatStringsSep "," cfg.enableAdmissionPlugins} \
"--authorization-webhook-config-file=${cfg.webhookConfig}" --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \
} \ ${optionalString (cfg.etcd.caFile != null) "--etcd-cafile=${cfg.etcd.caFile}"} \
--bind-address=${cfg.bindAddress} \ ${optionalString (cfg.etcd.certFile != null) "--etcd-certfile=${cfg.etcd.certFile}"} \
${optionalString (cfg.advertiseAddress != null) ${optionalString (cfg.etcd.keyFile != null) "--etcd-keyfile=${cfg.etcd.keyFile}"} \
"--advertise-address=${cfg.advertiseAddress}"} \ ${
${optionalString (cfg.clientCaFile != null) optionalString (cfg.featureGates != { })
"--client-ca-file=${cfg.clientCaFile}"} \ "--feature-gates=${
--disable-admission-plugins=${concatStringsSep "," cfg.disableAdmissionPlugins} \ (concatStringsSep "," (
--enable-admission-plugins=${concatStringsSep "," cfg.enableAdmissionPlugins} \ builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates)
--etcd-servers=${concatStringsSep "," cfg.etcd.servers} \ ))
${optionalString (cfg.etcd.caFile != null) }"
"--etcd-cafile=${cfg.etcd.caFile}"} \ } \
${optionalString (cfg.etcd.certFile != null) ${optionalString (cfg.basicAuthFile != null) "--basic-auth-file=${cfg.basicAuthFile}"} \
"--etcd-certfile=${cfg.etcd.certFile}"} \ ${
${optionalString (cfg.etcd.keyFile != null) optionalString (
"--etcd-keyfile=${cfg.etcd.keyFile}"} \ cfg.kubeletClientCaFile != null
${optionalString (cfg.featureGates != {}) ) "--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"
"--feature-gates=${(concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates)))}"} \ } \
${optionalString (cfg.basicAuthFile != null) ${
"--basic-auth-file=${cfg.basicAuthFile}"} \ optionalString (
${optionalString (cfg.kubeletClientCaFile != null) cfg.kubeletClientCertFile != null
"--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"} \ ) "--kubelet-client-certificate=${cfg.kubeletClientCertFile}"
${optionalString (cfg.kubeletClientCertFile != null) } \
"--kubelet-client-certificate=${cfg.kubeletClientCertFile}"} \ ${
${optionalString (cfg.kubeletClientKeyFile != null) optionalString (cfg.kubeletClientKeyFile != null) "--kubelet-client-key=${cfg.kubeletClientKeyFile}"
"--kubelet-client-key=${cfg.kubeletClientKeyFile}"} \ } \
${optionalString (cfg.preferredAddressTypes != null) ${
"--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"} \ optionalString (
${optionalString (cfg.proxyClientCertFile != null) cfg.preferredAddressTypes != null
"--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \ ) "--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"
${optionalString (cfg.proxyClientKeyFile != null) } \
"--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \ ${
${optionalString (cfg.runtimeConfig != "") optionalString (
"--runtime-config=${cfg.runtimeConfig}"} \ cfg.proxyClientCertFile != null
--secure-port=${toString cfg.securePort} \ ) "--proxy-client-cert-file=${cfg.proxyClientCertFile}"
--api-audiences=${toString cfg.apiAudiences} \ } \
--service-account-issuer=${toString cfg.serviceAccountIssuer} \ ${
--service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \ optionalString (cfg.proxyClientKeyFile != null) "--proxy-client-key-file=${cfg.proxyClientKeyFile}"
--service-account-key-file=${cfg.serviceAccountKeyFile} \ } \
--service-cluster-ip-range=${cfg.serviceClusterIpRange} \ ${optionalString (cfg.runtimeConfig != "") "--runtime-config=${cfg.runtimeConfig}"} \
--storage-backend=${cfg.storageBackend} \ --secure-port=${toString cfg.securePort} \
${optionalString (cfg.tlsCertFile != null) --api-audiences=${toString cfg.apiAudiences} \
"--tls-cert-file=${cfg.tlsCertFile}"} \ --service-account-issuer=${toString cfg.serviceAccountIssuer} \
${optionalString (cfg.tlsKeyFile != null) --service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \
"--tls-private-key-file=${cfg.tlsKeyFile}"} \ --service-account-key-file=${cfg.serviceAccountKeyFile} \
${optionalString (cfg.tokenAuthFile != null) --service-cluster-ip-range=${cfg.serviceClusterIpRange} \
"--token-auth-file=${cfg.tokenAuthFile}"} \ --storage-backend=${cfg.storageBackend} \
${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \ ${optionalString (cfg.tlsCertFile != null) "--tls-cert-file=${cfg.tlsCertFile}"} \
${cfg.extraOpts} ${optionalString (cfg.tlsKeyFile != null) "--tls-private-key-file=${cfg.tlsKeyFile}"} \
''; ${optionalString (cfg.tokenAuthFile != null) "--token-auth-file=${cfg.tokenAuthFile}"} \
WorkingDirectory = top.dataDir; ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
User = "kubernetes"; ${cfg.extraOpts}
Group = "kubernetes"; '';
AmbientCapabilities = "cap_net_bind_service"; WorkingDirectory = top.dataDir;
Restart = "on-failure"; User = "kubernetes";
RestartSec = 5; Group = "kubernetes";
}; AmbientCapabilities = "cap_net_bind_service";
Restart = "on-failure";
unitConfig = { RestartSec = 5;
StartLimitIntervalSec = 0;
};
}; };
services.etcd = { unitConfig = {
clientCertAuth = mkDefault true; StartLimitIntervalSec = 0;
peerClientCertAuth = mkDefault true;
listenClientUrls = mkDefault ["https://0.0.0.0:2379"];
listenPeerUrls = mkDefault ["https://0.0.0.0:2380"];
advertiseClientUrls = mkDefault ["https://${top.masterAddress}:2379"];
initialCluster = mkDefault ["${top.masterAddress}=https://${top.masterAddress}:2380"];
name = mkDefault top.masterAddress;
initialAdvertisePeerUrls = mkDefault ["https://${top.masterAddress}:2380"];
}; };
};
services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled { services.etcd = {
clientCertAuth = mkDefault true;
peerClientCertAuth = mkDefault true;
listenClientUrls = mkDefault [ "https://0.0.0.0:2379" ];
listenPeerUrls = mkDefault [ "https://0.0.0.0:2380" ];
advertiseClientUrls = mkDefault [ "https://${top.masterAddress}:2379" ];
initialCluster = mkDefault [ "${top.masterAddress}=https://${top.masterAddress}:2380" ];
name = mkDefault top.masterAddress;
initialAdvertisePeerUrls = mkDefault [ "https://${top.masterAddress}:2380" ];
};
apiserver-kubelet-api-admin-crb = { services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled {
apiVersion = "rbac.authorization.k8s.io/v1";
kind = "ClusterRoleBinding"; apiserver-kubelet-api-admin-crb = {
metadata = { apiVersion = "rbac.authorization.k8s.io/v1";
name = "system:kube-apiserver:kubelet-api-admin"; kind = "ClusterRoleBinding";
}; metadata = {
roleRef = { name = "system:kube-apiserver:kubelet-api-admin";
apiGroup = "rbac.authorization.k8s.io"; };
kind = "ClusterRole"; roleRef = {
name = "system:kubelet-api-admin"; apiGroup = "rbac.authorization.k8s.io";
}; kind = "ClusterRole";
subjects = [{ name = "system:kubelet-api-admin";
};
subjects = [
{
kind = "User"; kind = "User";
name = "system:kube-apiserver"; name = "system:kube-apiserver";
}]; }
}; ];
}; };
};
services.kubernetes.pki.certs = with top.lib; { services.kubernetes.pki.certs = with top.lib; {
apiServer = mkCert { apiServer = mkCert {
name = "kube-apiserver"; name = "kube-apiserver";
CN = "kubernetes"; CN = "kubernetes";
hosts = [ hosts = [
"kubernetes.default.svc" "kubernetes.default.svc"
"kubernetes.default.svc.${top.addons.dns.clusterDomain}" "kubernetes.default.svc.${top.addons.dns.clusterDomain}"
cfg.advertiseAddress cfg.advertiseAddress
top.masterAddress top.masterAddress
apiserverServiceIP apiserverServiceIP
"127.0.0.1" "127.0.0.1"
] ++ cfg.extraSANs; ] ++ cfg.extraSANs;
action = "systemctl restart kube-apiserver.service"; action = "systemctl restart kube-apiserver.service";
}; };
apiserverProxyClient = mkCert { apiserverProxyClient = mkCert {
@ -470,11 +521,11 @@ in
name = "etcd"; name = "etcd";
CN = top.masterAddress; CN = top.masterAddress;
hosts = [ hosts = [
"etcd.local" "etcd.local"
"etcd.${top.addons.dns.clusterDomain}" "etcd.${top.addons.dns.clusterDomain}"
top.masterAddress top.masterAddress
cfg.advertiseAddress cfg.advertiseAddress
]; ];
privateKeyOwner = "etcd"; privateKeyOwner = "etcd";
action = "systemctl restart etcd.service"; action = "systemctl restart etcd.service";
}; };

View file

@ -1,4 +1,10 @@
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
with lib; with lib;
@ -9,7 +15,10 @@ let
in in
{ {
imports = [ imports = [
(mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"]) (mkRenamedOptionModule
[ "services" "kubernetes" "proxy" "address" ]
[ "services" "kubernetes" "proxy" "bindAddress" ]
)
]; ];
###### interface ###### interface
@ -62,16 +71,24 @@ in
description = "Kubernetes Proxy Service"; description = "Kubernetes Proxy Service";
wantedBy = [ "kubernetes.target" ]; wantedBy = [ "kubernetes.target" ];
after = [ "kube-apiserver.service" ]; after = [ "kube-apiserver.service" ];
path = with pkgs; [ iptables conntrack-tools ]; path = with pkgs; [
iptables
conntrack-tools
];
serviceConfig = { serviceConfig = {
Slice = "kubernetes.slice"; Slice = "kubernetes.slice";
ExecStart = '' ExecStart = ''
${top.package}/bin/kube-proxy \ ${top.package}/bin/kube-proxy \
--bind-address=${cfg.bindAddress} \ --bind-address=${cfg.bindAddress} \
${optionalString (top.clusterCidr!=null) ${optionalString (top.clusterCidr != null) "--cluster-cidr=${top.clusterCidr}"} \
"--cluster-cidr=${top.clusterCidr}"} \ ${
${optionalString (cfg.featureGates != {}) optionalString (cfg.featureGates != { })
"--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \ "--feature-gates=${
concatStringsSep "," (
builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates)
)
}"
} \
--hostname-override=${cfg.hostname} \ --hostname-override=${cfg.hostname} \
--kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \ --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \

View file

@ -1,4 +1,10 @@
{ config, lib, options, pkgs, ... }: {
config,
lib,
options,
pkgs,
...
}:
let let
cfg = config.services.slurm; cfg = config.services.slurm;
@ -7,51 +13,51 @@ let
defaultUser = "slurm"; defaultUser = "slurm";
configFile = pkgs.writeTextDir "slurm.conf" configFile = pkgs.writeTextDir "slurm.conf" ''
'' ClusterName=${cfg.clusterName}
ClusterName=${cfg.clusterName} StateSaveLocation=${cfg.stateSaveLocation}
StateSaveLocation=${cfg.stateSaveLocation} SlurmUser=${cfg.user}
SlurmUser=${cfg.user} ${lib.optionalString (cfg.controlMachine != null) "controlMachine=${cfg.controlMachine}"}
${lib.optionalString (cfg.controlMachine != null) "controlMachine=${cfg.controlMachine}"} ${lib.optionalString (cfg.controlAddr != null) "controlAddr=${cfg.controlAddr}"}
${lib.optionalString (cfg.controlAddr != null) "controlAddr=${cfg.controlAddr}"} ${toString (map (x: "NodeName=${x}\n") cfg.nodeName)}
${toString (map (x: "NodeName=${x}\n") cfg.nodeName)} ${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)}
${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)} PlugStackConfig=${plugStackConfig}/plugstack.conf
PlugStackConfig=${plugStackConfig}/plugstack.conf ProctrackType=${cfg.procTrackType}
ProctrackType=${cfg.procTrackType} ${cfg.extraConfig}
${cfg.extraConfig} '';
'';
plugStackConfig = pkgs.writeTextDir "plugstack.conf" plugStackConfig = pkgs.writeTextDir "plugstack.conf" ''
'' ${lib.optionalString cfg.enableSrunX11 "optional ${pkgs.slurm-spank-x11}/lib/x11.so"}
${lib.optionalString cfg.enableSrunX11 "optional ${pkgs.slurm-spank-x11}/lib/x11.so"} ${cfg.extraPlugstackConfig}
${cfg.extraPlugstackConfig} '';
'';
cgroupConfig = pkgs.writeTextDir "cgroup.conf" cgroupConfig = pkgs.writeTextDir "cgroup.conf" ''
'' ${cfg.extraCgroupConfig}
${cfg.extraCgroupConfig} '';
'';
mpiConf = pkgs.writeTextDir "mpi.conf" mpiConf = pkgs.writeTextDir "mpi.conf" ''
'' PMIxCliTmpDirBase=${cfg.mpi.PmixCliTmpDirBase}
PMIxCliTmpDirBase=${cfg.mpi.PmixCliTmpDirBase} ${cfg.mpi.extraMpiConfig}
${cfg.mpi.extraMpiConfig} '';
'';
slurmdbdConf = pkgs.writeText "slurmdbd.conf" slurmdbdConf = pkgs.writeText "slurmdbd.conf" ''
'' DbdHost=${cfg.dbdserver.dbdHost}
DbdHost=${cfg.dbdserver.dbdHost} SlurmUser=${cfg.user}
SlurmUser=${cfg.user} StorageType=accounting_storage/mysql
StorageType=accounting_storage/mysql StorageUser=${cfg.dbdserver.storageUser}
StorageUser=${cfg.dbdserver.storageUser} ${cfg.dbdserver.extraConfig}
${cfg.dbdserver.extraConfig} '';
'';
# slurm expects some additional config files to be # slurm expects some additional config files to be
# in the same directory as slurm.conf # in the same directory as slurm.conf
etcSlurm = pkgs.symlinkJoin { etcSlurm = pkgs.symlinkJoin {
name = "etc-slurm"; name = "etc-slurm";
paths = [ configFile cgroupConfig plugStackConfig mpiConf ] ++ cfg.extraConfigPaths; paths = [
configFile
cgroupConfig
plugStackConfig
mpiConf
] ++ cfg.extraConfigPaths;
}; };
in in
@ -134,11 +140,13 @@ in
''; '';
}; };
package = lib.mkPackageOption pkgs "slurm" { package =
example = "slurm-full"; lib.mkPackageOption pkgs "slurm" {
} // { example = "slurm-full";
default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; }; }
}; // {
default = pkgs.slurm.override { enableX11 = !cfg.enableSrunX11; };
};
controlMachine = lib.mkOption { controlMachine = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
@ -173,7 +181,7 @@ in
nodeName = lib.mkOption { nodeName = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = []; default = [ ];
example = lib.literalExpression ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];''; example = lib.literalExpression ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
description = '' description = ''
Name that SLURM uses to refer to a node (or base partition for BlueGene Name that SLURM uses to refer to a node (or base partition for BlueGene
@ -184,7 +192,7 @@ in
partitionName = lib.mkOption { partitionName = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = []; default = [ ];
example = lib.literalExpression ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];''; example = lib.literalExpression ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
description = '' description = ''
Name by which the partition may be referenced. Note that now you have Name by which the partition may be referenced. Note that now you have
@ -285,7 +293,7 @@ in
extraConfigPaths = lib.mkOption { extraConfigPaths = lib.mkOption {
type = with lib.types; listOf path; type = with lib.types; listOf path;
default = []; default = [ ];
description = '' description = ''
Slurm expects config files for plugins in the same path Slurm expects config files for plugins in the same path
as `slurm.conf`. Add extra nix store as `slurm.conf`. Add extra nix store
@ -354,107 +362,132 @@ in
''; '';
}; };
in lib.mkIf ( cfg.enableStools || in
cfg.client.enable || lib.mkIf (cfg.enableStools || cfg.client.enable || cfg.server.enable || cfg.dbdserver.enable) {
cfg.server.enable ||
cfg.dbdserver.enable ) {
environment.systemPackages = [ wrappedSlurm ]; environment.systemPackages = [ wrappedSlurm ];
services.munge.enable = lib.mkDefault true; services.munge.enable = lib.mkDefault true;
# use a static uid as default to ensure it is the same on all nodes # use a static uid as default to ensure it is the same on all nodes
users.users.slurm = lib.mkIf (cfg.user == defaultUser) { users.users.slurm = lib.mkIf (cfg.user == defaultUser) {
name = defaultUser; name = defaultUser;
group = "slurm"; group = "slurm";
uid = config.ids.uids.slurm; uid = config.ids.uids.slurm;
}; };
users.groups.slurm.gid = config.ids.uids.slurm; users.groups.slurm.gid = config.ids.uids.slurm;
systemd.services.slurmd = lib.mkIf (cfg.client.enable) { systemd.services.slurmd = lib.mkIf (cfg.client.enable) {
path = with pkgs; [ wrappedSlurm coreutils ] path =
++ lib.optional cfg.enableSrunX11 slurm-spank-x11; with pkgs;
[
wrappedSlurm
coreutils
]
++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ after = [
"systemd-tmpfiles-clean.service" "systemd-tmpfiles-clean.service"
"munge.service" "munge.service"
"network-online.target" "network-online.target"
"remote-fs.target" "remote-fs.target"
];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "forking";
KillMode = "process";
ExecStart = "${wrappedSlurm}/bin/slurmd";
PIDFile = "/run/slurmd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
LimitMEMLOCK = "infinity";
Delegate = "Yes";
};
};
systemd.tmpfiles.rules = lib.optionals cfg.client.enable [
"d /var/spool/slurmd 755 root root -"
"d ${cfg.mpi.PmixCliTmpDirBase} 755 root root -"
]; ];
wants = [ "network-online.target" ];
serviceConfig = { services.openssh.settings.X11Forwarding = lib.mkIf cfg.client.enable (lib.mkDefault true);
Type = "forking";
KillMode = "process";
ExecStart = "${wrappedSlurm}/bin/slurmd";
PIDFile = "/run/slurmd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
LimitMEMLOCK = "infinity";
Delegate="Yes";
};
};
systemd.tmpfiles.rules = lib.optionals cfg.client.enable [ systemd.services.slurmctld = lib.mkIf (cfg.server.enable) {
"d /var/spool/slurmd 755 root root -" path =
"d ${cfg.mpi.PmixCliTmpDirBase} 755 root root -" with pkgs;
]; [
wrappedSlurm
munge
coreutils
]
++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
services.openssh.settings.X11Forwarding = lib.mkIf cfg.client.enable (lib.mkDefault true); wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"munged.service"
];
requires = [ "munged.service" ];
systemd.services.slurmctld = lib.mkIf (cfg.server.enable) { serviceConfig = {
path = with pkgs; [ wrappedSlurm munge coreutils ] Type = "forking";
++ lib.optional cfg.enableSrunX11 slurm-spank-x11; ExecStart = "${wrappedSlurm}/bin/slurmctld";
PIDFile = "/run/slurmctld.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
};
wantedBy = [ "multi-user.target" ]; preStart = ''
after = [ "network.target" "munged.service" ]; mkdir -p ${cfg.stateSaveLocation}
requires = [ "munged.service" ]; chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation}
'';
serviceConfig = {
Type = "forking";
ExecStart = "${wrappedSlurm}/bin/slurmctld";
PIDFile = "/run/slurmctld.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
}; };
preStart = '' systemd.services.slurmdbd =
mkdir -p ${cfg.stateSaveLocation} let
chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation} # slurm strips the last component off the path
''; configPath = "$RUNTIME_DIRECTORY/slurmdbd.conf";
in
lib.mkIf (cfg.dbdserver.enable) {
path = with pkgs; [
wrappedSlurm
munge
coreutils
];
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"munged.service"
"mysql.service"
];
requires = [
"munged.service"
"mysql.service"
];
preStart = ''
install -m 600 -o ${cfg.user} -T ${slurmdbdConf} ${configPath}
${lib.optionalString (cfg.dbdserver.storagePassFile != null) ''
echo "StoragePass=$(cat ${cfg.dbdserver.storagePassFile})" \
>> ${configPath}
''}
'';
script = ''
export SLURM_CONF=${configPath}
exec ${cfg.package}/bin/slurmdbd -D
'';
serviceConfig = {
RuntimeDirectory = "slurmdbd";
Type = "simple";
PIDFile = "/run/slurmdbd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
};
};
}; };
systemd.services.slurmdbd = let
# slurm strips the last component off the path
configPath = "$RUNTIME_DIRECTORY/slurmdbd.conf";
in lib.mkIf (cfg.dbdserver.enable) {
path = with pkgs; [ wrappedSlurm munge coreutils ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "munged.service" "mysql.service" ];
requires = [ "munged.service" "mysql.service" ];
preStart = ''
install -m 600 -o ${cfg.user} -T ${slurmdbdConf} ${configPath}
${lib.optionalString (cfg.dbdserver.storagePassFile != null) ''
echo "StoragePass=$(cat ${cfg.dbdserver.storagePassFile})" \
>> ${configPath}
''}
'';
script = ''
export SLURM_CONF=${configPath}
exec ${cfg.package}/bin/slurmdbd -D
'';
serviceConfig = {
RuntimeDirectory = "slurmdbd";
Type = "simple";
PIDFile = "/run/slurmdbd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
};
};
};
} }

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