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

treewide: Format all Nix files

Format all Nix files using the officially approved formatter,
making the CI check introduced in the previous commit succeed:

  nix-build ci -A fmt.check

This is the next step of the of the [implementation](https://github.com/NixOS/nixfmt/issues/153)
of the accepted [RFC 166](https://github.com/NixOS/rfcs/pull/166).

This commit will lead to merge conflicts for a number of PRs,
up to an estimated ~1100 (~33%) among the PRs with activity in the past 2
months, but that should be lower than what it would be without the previous
[partial treewide format](https://github.com/NixOS/nixpkgs/pull/322537).

Merge conflicts caused by this commit can now automatically be resolved while rebasing using the
[auto-rebase script](8616af08d9/maintainers/scripts/auto-rebase).

If you run into any problems regarding any of this, please reach out to the
[formatting team](https://nixos.org/community/teams/formatting/) by
pinging @NixOS/nix-formatting.
This commit is contained in:
Silvan Mosberger 2025-04-01 20:10:43 +02:00
parent 2140bf39e4
commit 374e6bcc40
1523 changed files with 986047 additions and 513621 deletions

View file

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

View file

@ -8,31 +8,33 @@
# as subcomponents (e.g. the container feature, or nixops if network
# expressions are ever made modular at the top level) can just use
# types.submodule instead of using eval-config.nix
evalConfigArgs@
{ # !!! system can be set modularly, would be nice to remove,
evalConfigArgs@{
# !!! system can be set modularly, would be nice to remove,
# however, removing or changing this default is too much
# of a breaking change. To set it modularly, pass `null`.
system ? builtins.currentSystem
, # !!! is this argument needed any more? The pkgs argument can
system ? builtins.currentSystem,
# !!! is this argument needed any more? The pkgs argument can
# be set modularly anyway.
pkgs ? null
, # !!! what do we gain by making this configurable?
pkgs ? null,
# !!! what do we gain by making this configurable?
# we can add modules that are included in specialisations, regardless
# of inheritParentConfig.
baseModules ? import ../modules/module-list.nix
, # !!! See comment about args in lib/modules.nix
extraArgs ? {}
, # !!! See comment about args in lib/modules.nix
specialArgs ? {}
, modules
, modulesLocation ? (builtins.unsafeGetAttrPos "modules" evalConfigArgs).file or null
, # !!! See comment about check in lib/modules.nix
check ? true
, prefix ? []
, lib ? import ../../lib
, extraModules ?
let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
in lib.optional (e != "") (
baseModules ? import ../modules/module-list.nix,
# !!! See comment about args in lib/modules.nix
extraArgs ? { },
# !!! See comment about args in lib/modules.nix
specialArgs ? { },
modules,
modulesLocation ? (builtins.unsafeGetAttrPos "modules" evalConfigArgs).file or null,
# !!! See comment about check in lib/modules.nix
check ? true,
prefix ? [ ],
lib ? import ../../lib,
extraModules ?
let
e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
in
lib.optional (e != "") (
lib.warn
''
The NIXOS_EXTRA_MODULE_PATH environment variable is deprecated and will be
@ -51,18 +53,20 @@ evalConfigArgs@
''
# NOTE: this import call is unnecessary and it even removes the file name
# from error messages.
import e
)
import
e
),
}:
let
inherit (lib) optional;
evalModulesMinimal = (import ./default.nix {
inherit lib;
# Implicit use of feature is noted in implementation.
featureFlags.minimalModules = { };
}).evalModules;
evalModulesMinimal =
(import ./default.nix {
inherit lib;
# Implicit use of feature is noted in implementation.
featureFlags.minimalModules = { };
}).evalModules;
pkgsModule = rec {
_file = ./eval-config.nix;
@ -75,34 +79,39 @@ let
# they way through, but has the last priority behind everything else.
nixpkgs.system = lib.mkDefault system;
})
++
(optional (pkgs != null) {
++ (optional (pkgs != null) {
# This should be default priority, so it conflicts with any user-defined pkgs.
nixpkgs.pkgs = pkgs;
})
);
};
withWarnings = x:
lib.warnIf (evalConfigArgs?extraArgs) "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."
lib.warnIf (specialArgs?pkgs) ''
You have set specialArgs.pkgs, which means that options like nixpkgs.config
and nixpkgs.overlays will be ignored. If you wish to reuse an already created
pkgs, which you know is configured correctly for this NixOS configuration,
please import the `nixosModules.readOnlyPkgs` module from the nixpkgs flake or
`(modulesPath + "/misc/nixpkgs/read-only.nix"), and set `{ nixpkgs.pkgs = <your pkgs>; }`.
This properly disables the ignored options to prevent future surprises.
''
x;
withWarnings =
x:
lib.warnIf (evalConfigArgs ? extraArgs)
"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."
lib.warnIf
(specialArgs ? pkgs)
''
You have set specialArgs.pkgs, which means that options like nixpkgs.config
and nixpkgs.overlays will be ignored. If you wish to reuse an already created
pkgs, which you know is configured correctly for this NixOS configuration,
please import the `nixosModules.readOnlyPkgs` module from the nixpkgs flake or
`(modulesPath + "/misc/nixpkgs/read-only.nix"), and set `{ nixpkgs.pkgs = <your pkgs>; }`.
This properly disables the ignored options to prevent future surprises.
''
x;
legacyModules =
lib.optional (evalConfigArgs?extraArgs) {
lib.optional (evalConfigArgs ? extraArgs) {
config = {
_module.args = extraArgs;
};
}
++ lib.optional (evalConfigArgs?check) {
++ lib.optional (evalConfigArgs ? check) {
config = {
_module.check = lib.mkDefault check;
};
@ -118,29 +127,43 @@ let
else
map (lib.setDefaultModuleLocation modulesLocation) modules;
in
locatedModules ++ legacyModules;
locatedModules ++ legacyModules;
noUserModules = evalModulesMinimal ({
inherit prefix specialArgs;
modules = baseModules ++ extraModules ++ [ pkgsModule modulesModule ];
modules =
baseModules
++ extraModules
++ [
pkgsModule
modulesModule
];
});
# Extra arguments that are useful for constructing a similar configuration.
modulesModule = {
config = {
_module.args = {
inherit noUserModules baseModules extraModules modules;
inherit
noUserModules
baseModules
extraModules
modules
;
};
};
};
nixosWithUserModules = noUserModules.extendModules { modules = allUserModules; };
withExtraAttrs = configuration: configuration // {
inherit extraArgs;
inherit (configuration._module.args) pkgs;
inherit lib;
extendModules = args: withExtraAttrs (configuration.extendModules args);
};
withExtraAttrs =
configuration:
configuration
// {
inherit extraArgs;
inherit (configuration._module.args) pkgs;
inherit lib;
extendModules = args: withExtraAttrs (configuration.extendModules args);
};
in
withWarnings (withExtraAttrs nixosWithUserModules)

View file

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

View file

@ -703,28 +703,28 @@ let
''}
${lib.optionalString installBootLoader ''
# In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
# Use this option to create a symlink from vda to any arbitrary device you want.
${lib.optionalString (config.boot.loader.grub.enable) (
lib.concatMapStringsSep " " (
device:
lib.optionalString (device != "/dev/vda") ''
mkdir -p "$(dirname ${device})"
ln -s /dev/vda ${device}
''
) config.boot.loader.grub.devices
)}
# 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.
${lib.optionalString (config.boot.loader.grub.enable) (
lib.concatMapStringsSep " " (
device:
lib.optionalString (device != "/dev/vda") ''
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
# clobbers $HOME/.nix-defexpr/channels/nixos This would cause a folder
# /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
# __noChroot for example).
export HOME=$TMPDIR
NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
''}
# NOTE: systemd-boot-builder.py calls nix-env --list-generations which
# clobbers $HOME/.nix-defexpr/channels/nixos This would cause a folder
# /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
# __noChroot for example).
export HOME=$TMPDIR
NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
''}
# Set the ownerships of the contents. The modes are set in preVM.
# No globbing on targets, so no need to set -f

View file

@ -1,18 +1,22 @@
{ lib, stdenv, squashfsTools, closureInfo
{
lib,
stdenv,
squashfsTools,
closureInfo,
, fileName ? "squashfs"
, # The root directory of the squashfs filesystem is filled with the
fileName ? "squashfs",
# The root directory of the squashfs filesystem is filled with the
# closures of the Nix store paths listed here.
storeContents ? []
storeContents ? [ ],
# Pseudo files to be added to squashfs image
, pseudoFiles ? []
, noStrip ? false
, # Compression parameters.
pseudoFiles ? [ ],
noStrip ? false,
# Compression parameters.
# For zstd compression you can use "zstd -Xcompression-level 6".
comp ? "xz -Xdict-size 100%"
, # create hydra build product. will put image in directory instead
comp ? "xz -Xdict-size 100%",
# create hydra build product. will put image in directory instead
# of directly in the store
hydraBuildProduct ? false
hydraBuildProduct ? false,
}:
let
@ -34,24 +38,28 @@ stdenv.mkDerivation {
cp $closureInfo/registration nix-path-registration
imgPath="$out"
'' + lib.optionalString hydraBuildProduct ''
''
+ lib.optionalString hydraBuildProduct ''
mkdir $out
imgPath="$out/${fileName}.squashfs"
'' + lib.optionalString stdenv.buildPlatform.is32bit ''
''
+ lib.optionalString stdenv.buildPlatform.is32bit ''
# 64 cores on i686 does not work
# fails with FATAL ERROR: mangle2:: xz compress failed with error code 5
if ((NIX_BUILD_CORES > 48)); then
NIX_BUILD_CORES=48
fi
'' + ''
''
+ ''
# Generate the squashfs image.
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} \
-processors $NIX_BUILD_CORES -root-mode 0755
'' + lib.optionalString hydraBuildProduct ''
''
+ lib.optionalString hydraBuildProduct ''
mkdir -p $out/nix-support
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" [
(assertValueOneOf "Type" [
"exec" "simple" "forking" "oneshot" "dbus" "notify" "notify-reload" "idle"
"exec"
"simple"
"forking"
"oneshot"
"dbus"
"notify"
"notify-reload"
"idle"
])
(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 {
name = "systemd option";
merge = loc: defs:
merge =
loc: defs:
let
defs' = filterOverrides defs;
in
if any (def: isList def.value) defs'
then concatMap (def: toList def.value) defs'
else mergeEqualOption loc defs';
if any (def: isList def.value) defs' then
concatMap (def: toList def.value) defs'
else
mergeEqualOption loc defs';
};
sharedOptions = {
@ -76,7 +91,10 @@ in rec {
overrideStrategy = mkOption {
default = "asDropinIfExists";
type = types.enum [ "asDropinIfExists" "asDropin" ];
type = types.enum [
"asDropinIfExists"
"asDropin"
];
description = ''
Defines how unit configuration is provided for systemd:
@ -91,7 +109,7 @@ in rec {
};
requiredBy = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Units that require (i.e. depend on and need to go down with) this unit.
@ -101,7 +119,7 @@ in rec {
};
upheldBy = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Keep this unit running as long as the listed units are running. This is a continuously
@ -110,7 +128,7 @@ in rec {
};
wantedBy = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Units that want (i.e. depend on) this unit. The default method for
@ -128,7 +146,7 @@ in rec {
};
aliases = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = "Aliases of that unit.";
};
@ -160,13 +178,13 @@ in rec {
};
documentation = mkOption {
default = [];
default = [ ];
type = types.listOf types.str;
description = "A list of URIs referencing documentation for this unit or its configuration.";
};
requires = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Start the specified units when this unit is started, and stop
@ -175,7 +193,7 @@ in rec {
};
wants = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Start the specified units when this unit is started.
@ -183,7 +201,7 @@ in rec {
};
upholds = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Keeps the specified running while this unit is running. A continuous version of `wants`.
@ -191,7 +209,7 @@ in rec {
};
after = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
If the specified units are started at the same time as
@ -200,7 +218,7 @@ in rec {
};
before = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
If the specified units are started at the same time as
@ -209,7 +227,7 @@ in rec {
};
bindsTo = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Like requires, but in addition, if the specified units
@ -218,7 +236,7 @@ in rec {
};
partOf = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
If the specified units are stopped or restarted, then this
@ -227,7 +245,7 @@ in rec {
};
conflicts = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
If the specified units are started, then this unit is stopped
@ -236,7 +254,7 @@ in rec {
};
requisite = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
Similar to requires. However if the units listed are not started,
@ -245,8 +263,10 @@ in rec {
};
unitConfig = mkOption {
default = {};
example = { RequiresMountsFor = "/data"; };
default = { };
example = {
RequiresMountsFor = "/data";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -256,7 +276,7 @@ in rec {
};
onFailure = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
A list of one or more units that are activated when
@ -265,7 +285,7 @@ in rec {
};
onSuccess = mkOption {
default = [];
default = [ ];
type = types.listOf unitNameType;
description = ''
A list of one or more units that are activated when
@ -274,21 +294,21 @@ in rec {
};
startLimitBurst = mkOption {
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time
interval are not permitted to start any more.
'';
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time
interval are not permitted to start any more.
'';
};
startLimitIntervalSec = mkOption {
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time
interval are not permitted to start any more.
'';
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
more than startLimitBurst times within an interval time
interval are not permitted to start any more.
'';
};
};
@ -301,7 +321,7 @@ in rec {
options = {
restartTriggers = mkOption {
default = [];
default = [ ];
type = types.listOf types.unspecified;
description = ''
An arbitrary list of items such as derivations. If any item
@ -311,7 +331,7 @@ in rec {
};
reloadTriggers = mkOption {
default = [];
default = [ ];
type = types.listOf unitOption;
description = ''
An arbitrary list of items such as derivations. If any item
@ -324,170 +344,188 @@ in rec {
};
stage1CommonUnitOptions = commonUnitOptions;
serviceOptions = { name, config, ... }: {
options = {
serviceOptions =
{ name, config, ... }:
{
options = {
environment = mkOption {
default = {};
type = with types; attrsOf (nullOr (oneOf [ str path package ]));
example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
description = "Environment variables passed to the service's processes.";
};
path = mkOption {
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;
environment = mkOption {
default = { };
type =
with types;
attrsOf (
nullOr (oneOf [
str
path
package
])
);
example = {
PATH = "/foo/bar/bin";
LANG = "nl_NL.UTF-8";
};
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.
'';
description = "Environment variables passed to the service's processes.";
};
path = mkOption {
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 {
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 = [];
};
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;
})
];
};
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 = {
imports = [
stage2CommonUnitOptions
@ -549,7 +587,7 @@ in rec {
startAt = mkOption {
type = with types; either str (listOf str);
default = [];
default = [ ];
example = "Sun 14:00:00";
description = ''
Automatically start this unit at the given date/time, which
@ -570,14 +608,16 @@ in rec {
];
};
socketOptions = {
options = {
listenStreams = mkOption {
default = [];
default = [ ];
type = types.listOf types.str;
example = [ "0.0.0.0:993" "/run/my-socket" ];
example = [
"0.0.0.0:993"
"/run/my-socket"
];
description = ''
For each item in this list, a `ListenStream`
option in the `[Socket]` section will be created.
@ -585,9 +625,12 @@ in rec {
};
listenDatagrams = mkOption {
default = [];
default = [ ];
type = types.listOf types.str;
example = [ "0.0.0.0:993" "/run/my-socket" ];
example = [
"0.0.0.0:993"
"/run/my-socket"
];
description = ''
For each item in this list, a `ListenDatagram`
option in the `[Socket]` section will be created.
@ -595,8 +638,10 @@ in rec {
};
socketConfig = mkOption {
default = {};
example = { ListenStream = "/run/my-socket"; };
default = { };
example = {
ListenStream = "/run/my-socket";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -622,13 +667,15 @@ in rec {
];
};
timerOptions = {
options = {
timerConfig = mkOption {
default = {};
example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
default = { };
example = {
OnCalendar = "Sun 14:00:00";
Unit = "foo.service";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -655,13 +702,15 @@ in rec {
];
};
pathOptions = {
options = {
pathConfig = mkOption {
default = {};
example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
default = { };
example = {
PathChanged = "/some/path";
Unit = "changedpath.service";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -687,7 +736,6 @@ in rec {
];
};
mountOptions = {
options = {
@ -721,8 +769,10 @@ in rec {
};
mountConfig = mkOption {
default = {};
example = { DirectoryMode = "0775"; };
default = { };
example = {
DirectoryMode = "0775";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -761,8 +811,10 @@ in rec {
};
automountConfig = mkOption {
default = {};
example = { DirectoryMode = "0775"; };
default = { };
example = {
DirectoryMode = "0775";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the
@ -792,8 +844,10 @@ in rec {
options = {
sliceConfig = mkOption {
default = {};
example = { MemoryMax = "2G"; };
default = { };
example = {
MemoryMax = "2G";
};
type = types.attrsOf unitOption;
description = ''
Each attribute in this set specifies an option in the

View file

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

View file

@ -1,9 +1,14 @@
# 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
copyChannel = true;
format = "qcow2";
format = "qcow2";
in
{
imports = [
@ -17,16 +22,20 @@ in
system.nixos.tags = [ "openstack" ];
system.build.image = config.system.build.openstackImage;
system.build.openstackImage = import ../../../lib/make-disk-image.nix {
inherit lib config copyChannel format;
inherit
lib
config
copyChannel
format
;
inherit (config.image) baseName;
additionalSpace = "1024M";
pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
configFile = pkgs.writeText "configuration.nix"
''
{
imports = [ <nixpkgs/nixos/modules/virtualisation/openstack-config.nix> ];
}
'';
configFile = pkgs.writeText "configuration.nix" ''
{
imports = [ <nixpkgs/nixos/modules/virtualisation/openstack-config.nix> ];
}
'';
};
}

View file

@ -1,18 +1,26 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
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);
optimizedKeymap = pkgs.runCommand "keymap" {
nativeBuildInputs = [ pkgs.buildPackages.kbd ];
LOADKEYS_KEYMAP_PATH = "${consoleEnv pkgs.kbd}/share/keymaps/**";
preferLocalBuild = true;
} ''
loadkeys -b ${lib.optionalString isUnicode "-u"} "${cfg.keyMap}" > $out
'';
optimizedKeymap =
pkgs.runCommand "keymap"
{
nativeBuildInputs = [ pkgs.buildPackages.kbd ];
LOADKEYS_KEYMAP_PATH = "${consoleEnv pkgs.kbd}/share/keymaps/**";
preferLocalBuild = true;
}
''
loadkeys -b ${lib.optionalString isUnicode "-u"} "${cfg.keyMap}" > $out
'';
# Sadly, systemd-vconsole-setup doesn't support binary keymaps.
vconsoleConf = pkgs.writeText "vconsole.conf" ''
@ -20,22 +28,24 @@ let
${lib.optionalString (cfg.font != null) "FONT=${cfg.font}"}
'';
consoleEnv = kbd: pkgs.buildEnv {
name = "console-env";
paths = [ kbd ] ++ cfg.packages;
pathsToLink = [
"/share/consolefonts"
"/share/consoletrans"
"/share/keymaps"
"/share/unimaps"
];
};
consoleEnv =
kbd:
pkgs.buildEnv {
name = "console-env";
paths = [ kbd ] ++ cfg.packages;
pathsToLink = [
"/share/consolefonts"
"/share/consoletrans"
"/share/keymaps"
"/share/unimaps"
];
};
in
{
###### interface
options.console = {
options.console = {
enable = lib.mkEnableOption "virtual console" // {
default = true;
};
@ -70,10 +80,22 @@ in
type = with lib.types; listOf (strMatching "[[:xdigit:]]{6}");
default = [ ];
example = [
"002b36" "dc322f" "859900" "b58900"
"268bd2" "d33682" "2aa198" "eee8d5"
"002b36" "cb4b16" "586e75" "657b83"
"839496" "6c71c4" "93a1a1" "fdf6e3"
"002b36"
"dc322f"
"859900"
"b58900"
"268bd2"
"d33682"
"2aa198"
"eee8d5"
"002b36"
"cb4b16"
"586e75"
"657b83"
"839496"
"6c71c4"
"93a1a1"
"fdf6e3"
];
description = ''
The 16 colors palette used by the virtual consoles.
@ -112,103 +134,127 @@ in
};
###### implementation
config = lib.mkMerge [
{ console.keyMap = with config.services.xserver;
lib.mkIf cfg.useXkbConfig
(pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
{
console.keyMap =
with config.services.xserver;
lib.mkIf cfg.useXkbConfig (
pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
'${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}' \
-option '${xkb.options}' -variant '${xkb.variant}' > "$out"
'');
''
);
}
(lib.mkIf cfg.enable (lib.mkMerge [
{ environment.systemPackages = [ pkgs.kbd ];
(lib.mkIf cfg.enable (
lib.mkMerge [
{
environment.systemPackages = [ pkgs.kbd ];
# Let systemd-vconsole-setup.service do the work of setting up the
# virtual consoles.
environment.etc."vconsole.conf".source = vconsoleConf;
# Provide kbd with additional packages.
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
# Let systemd-vconsole-setup.service do the work of setting up the
# virtual consoles.
environment.etc."vconsole.conf".source = vconsoleConf;
# Provide kbd with additional packages.
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
boot.initrd.preLVMCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (lib.mkBefore ''
kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
loadkmap < ${optimizedKeymap}
boot.initrd.preLVMCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
lib.mkBefore ''
kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
loadkmap < ${optimizedKeymap}
${lib.optionalString (cfg.earlySetup && cfg.font != null) ''
setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
''}
'');
${lib.optionalString (cfg.earlySetup && cfg.font != null) ''
setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
''}
''
);
boot.initrd.systemd.contents = {
"/etc/vconsole.conf".source = vconsoleConf;
# Add everything if we want full console setup...
"/etc/kbd" = lib.mkIf cfg.earlySetup { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share"; };
# ...but only the keymaps if we don't
"/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps"; };
};
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}"
];
systemd.additionalUpstreamSystemUnits = [
"systemd-vconsole-setup.service"
];
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.contents = {
"/etc/vconsole.conf".source = vconsoleConf;
# Add everything if we want full console setup...
"/etc/kbd" = lib.mkIf cfg.earlySetup {
source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share";
};
# ...but only the keymaps if we don't
"/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) {
source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps";
};
};
}
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 != []) {
boot.kernelParams = [
"vt.default_red=${makeColor 0 cfg.colors}"
"vt.default_grn=${makeColor 1 cfg.colors}"
"vt.default_blu=${makeColor 2 cfg.colors}"
];
})
systemd.additionalUpstreamSystemUnits = [
"systemd-vconsole-setup.service"
];
(lib.mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) {
boot.initrd.extraUtilsCommands = ''
mkdir -p $out/share/consolefonts
${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
'';
})
]))
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";
};
};
}
(lib.mkIf (cfg.colors != [ ]) {
boot.kernelParams = [
"vt.default_red=${makeColor 0 cfg.colors}"
"vt.default_grn=${makeColor 1 cfg.colors}"
"vt.default_blu=${makeColor 2 cfg.colors}"
];
})
(lib.mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) {
boot.initrd.extraUtilsCommands = ''
mkdir -p $out/share/consolefonts
${
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 = [

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
{
###### interface
@ -39,8 +44,11 @@
extraLocaleSettings = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {};
example = { LC_MESSAGES = "en_US.UTF-8"; LC_TIME = "de_DE.UTF-8"; };
default = { };
example = {
LC_MESSAGES = "en_US.UTF-8";
LC_TIME = "de_DE.UTF-8";
};
description = ''
A set of additional system-wide locale settings other than
`LANG` which can be configured with
@ -50,14 +58,18 @@
supportedLocales = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = lib.unique
(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
] ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
));
default = lib.unique (
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
]
++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
)
);
defaultText = lib.literalExpression ''
lib.unique
(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))
))
'';
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 = ''
List of locales that the system should support. The value
`"all"` means that all locales supported by
@ -81,30 +97,30 @@
};
###### implementation
config = {
environment.systemPackages =
# 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 =
{ LANG = config.i18n.defaultLocale;
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
} // config.i18n.extraLocaleSettings;
environment.sessionVariables = {
LANG = config.i18n.defaultLocale;
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
} // 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";
};
# /etc/locale.conf is used by systemd.
environment.etc."locale.conf".source = pkgs.writeText "locale.conf"
''
LANG=${config.i18n.defaultLocale}
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings)}
'';
environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
LANG=${config.i18n.defaultLocale}
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
)}
'';
};
}

View file

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

View file

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

View file

@ -6,8 +6,13 @@
- ./nix-flakes.nix
- ./nix-remote-build.nix
- nixos/modules/services/system/nix-daemon.nix
*/
{ config, lib, pkgs, ... }:
*/
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
@ -45,12 +50,14 @@ let
isNixAtLeast = versionAtLeast (getVersion nixPackage);
defaultSystemFeatures = [
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
] ++ optionals (pkgs.stdenv.hostPlatform ? gcc.arch) (
defaultSystemFeatures =
[
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
]
++ optionals (pkgs.stdenv.hostPlatform ? gcc.arch) (
# a builder can run code for `gcc.arch` and inferior architectures
[ "gccarch-${pkgs.stdenv.hostPlatform.gcc.arch}" ]
++ map (x: "gccarch-${x}") (
@ -73,19 +80,21 @@ let
systemFeatures = "system-features";
};
semanticConfType = with types;
semanticConfType =
with types;
let
confAtom = nullOr
(oneOf [
confAtom =
nullOr (oneOf [
bool
int
float
str
path
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
attrsOf (either confAtom (listOf confAtom));
@ -93,17 +102,28 @@ let
assert isNixAtLeast "2.2";
let
mkValueString = v:
if v == null then ""
else if isInt v then toString v
else if isBool v then boolToString v
else if isFloat v then floatToString v
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";
mkValueString =
v:
if v == null then
""
else if isInt v then
toString v
else if isBool v then
boolToString v
else if isFloat v then
floatToString v
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}";
@ -125,41 +145,71 @@ let
${cfg.extraOptions}
'';
checkPhase = lib.optionalString cfg.checkConfig (
if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
echo "Ignoring validation for cross-compilation"
''
if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then
''
echo "Ignoring validation for cross-compilation"
''
else
let
showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
in
''
echo "Validating generated nix.conf"
ln -s $out ./nix.conf
set -e
set +o pipefail
NIX_CONF_DIR=$PWD \
${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
|& sed -e 's/^warning:/error:/' \
| (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}')
set -o pipefail
'');
let
showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
in
''
echo "Validating generated nix.conf"
ln -s $out ./nix.conf
set -e
set +o pipefail
NIX_CONF_DIR=$PWD \
${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
|& sed -e 's/^warning:/error:/' \
| (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}')
set -o pipefail
''
);
};
in
{
imports = [
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; })
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; })
] ++
mapAttrsToList
(oldConf: newConf:
mkRenamedOptionModuleWith {
sinceRelease = 2205;
from = [ "nix" oldConf ];
to = [ "nix" "settings" newConf ];
imports =
[
(mkRenamedOptionModuleWith {
sinceRelease = 2003;
from = [
"nix"
"useChroot"
];
to = [
"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 = {
nix = {
@ -258,7 +308,10 @@ in
extra-sandbox-paths = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "/dev" "/proc" ];
example = [
"/dev"
"/proc"
];
description = ''
Directories from the host filesystem to be included
in the sandbox.
@ -314,7 +367,11 @@ in
trusted-users = mkOption {
type = types.listOf types.str;
example = [ "root" "alice" "@wheel" ];
example = [
"root"
"alice"
"@wheel"
];
description = ''
A list of names of users that have additional rights when
connecting to the Nix daemon, such as the ability to specify
@ -342,7 +399,12 @@ in
allowed-users = mkOption {
type = types.listOf types.str;
default = [ "*" ];
example = [ "@wheel" "@builders" "alice" "bob" ];
example = [
"@wheel"
"@builders"
"alice"
"bob"
];
description = ''
A list of names of users (separated by whitespace) that are
allowed to connect to the Nix daemon. As with

View file

@ -1,27 +1,35 @@
# This module defines a global environment configuration and
# a common configuration for all shells.
{ config, lib, utils, pkgs, ... }:
{
config,
lib,
utils,
pkgs,
...
}:
let
cfg = config.environment;
exportedEnvVars =
let
absoluteVariables =
lib.mapAttrs (n: lib.toList) cfg.variables;
absoluteVariables = lib.mapAttrs (n: lib.toList) cfg.variables;
suffixedVariables =
lib.flip lib.mapAttrs cfg.profileRelativeEnvVars (envVar: listSuffixes:
lib.concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles
);
suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeEnvVars (
envVar: listSuffixes:
lib.concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles
);
allVariables =
lib.zipAttrsWith (n: lib.concatLists) [ absoluteVariables suffixedVariables ];
allVariables = lib.zipAttrsWith (n: lib.concatLists) [
absoluteVariables
suffixedVariables
];
exportVariables =
lib.mapAttrsToList (n: v: ''export ${n}="${lib.concatStringsSep ":" v}"'') allVariables;
exportVariables = lib.mapAttrsToList (
n: v: ''export ${n}="${lib.concatStringsSep ":" v}"''
) allVariables;
in
lib.concatStringsSep "\n" exportVariables;
lib.concatStringsSep "\n" exportVariables;
in
{
@ -29,8 +37,11 @@ in
options = {
environment.variables = lib.mkOption {
default = {};
example = { EDITOR = "nvim"; VISUAL = "nvim"; };
default = { };
example = {
EDITOR = "nvim";
VISUAL = "nvim";
};
description = ''
A set of environment variables used in the global environment.
These variables will be set on shell initialisation (e.g. in /etc/profile).
@ -42,14 +53,32 @@ in
Setting a variable to `null` does nothing. You can override a
variable set by another module to `null` to unset it.
'';
type = with lib.types; attrsOf (nullOr (oneOf [ (listOf (oneOf [ int str path ])) int str path ]));
apply = let
toStr = v: if lib.isPath v then "${v}" else toString v;
in attrs: lib.mapAttrs (n: v: if lib.isList v then lib.concatMapStringsSep ":" toStr v else toStr v) (lib.filterAttrs (n: v: v != null) attrs);
type =
with lib.types;
attrsOf (
nullOr (oneOf [
(listOf (oneOf [
int
str
path
]))
int
str
path
])
);
apply =
let
toStr = v: if lib.isPath v then "${v}" else toString v;
in
attrs:
lib.mapAttrs (n: v: if lib.isList v then lib.concatMapStringsSep ":" toStr v else toStr v) (
lib.filterAttrs (n: v: v != null) attrs
);
};
environment.profiles = lib.mkOption {
default = [];
default = [ ];
description = ''
A list of profiles used to setup the global environment.
'';
@ -58,7 +87,13 @@ in
environment.profileRelativeEnvVars = lib.mkOption {
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 = ''
Attribute set of environment variable. Each attribute maps to a list
of relative paths. Each relative path is appended to the each profile
@ -110,7 +145,10 @@ in
};
environment.shellAliases = lib.mkOption {
example = { l = null; ll = "ls -l"; };
example = {
l = null;
ll = "ls -l";
};
description = ''
An attribute set that maps aliases (the top level attribute names in
this option) to command strings or directly to build outputs. The
@ -151,7 +189,7 @@ in
};
environment.shells = lib.mkOption {
default = [];
default = [ ];
example = lib.literalExpression "[ pkgs.bashInteractive pkgs.zsh ]";
description = ''
A list of permissible login shells for user accounts.
@ -178,49 +216,46 @@ in
environment.shellAliases = lib.mapAttrs (name: lib.mkDefault) {
ls = "ls --color=tty";
ll = "ls -l";
l = "ls -alh";
l = "ls -alh";
};
environment.etc.shells.text =
''
${lib.concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
/bin/sh
'';
environment.etc.shells.text = ''
${lib.concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
/bin/sh
'';
# For resetting environment with `. /etc/set-environment` when needed
# and discoverability (see motivation of #30418).
environment.etc.set-environment.source = config.system.build.setEnvironment;
system.build.setEnvironment = pkgs.writeText "set-environment"
''
# DO NOT EDIT -- this file has been generated automatically.
system.build.setEnvironment = pkgs.writeText "set-environment" ''
# DO NOT EDIT -- this file has been generated automatically.
# Prevent this file from being sourced by child shells.
export __NIXOS_SET_ENVIRONMENT_DONE=1
# Prevent this file from being sourced by child shells.
export __NIXOS_SET_ENVIRONMENT_DONE=1
${exportedEnvVars}
${exportedEnvVars}
${cfg.extraInit}
${cfg.extraInit}
${lib.optionalString cfg.homeBinInPath ''
# ~/bin if it exists overrides other bin directories.
export PATH="$HOME/bin:$PATH"
''}
${lib.optionalString cfg.homeBinInPath ''
# ~/bin if it exists overrides other bin directories.
export PATH="$HOME/bin:$PATH"
''}
${lib.optionalString cfg.localBinInPath ''
export PATH="$HOME/.local/bin:$PATH"
''}
'';
${lib.optionalString cfg.localBinInPath ''
export PATH="$HOME/.local/bin:$PATH"
''}
'';
system.activationScripts.binsh = lib.stringAfter [ "stdio" ]
''
# Create the required /bin/sh symlink; otherwise lots of things
# (notably the system() function) won't work.
mkdir -p /bin
chmod 0755 /bin
ln -sfn "${cfg.binsh}" /bin/.sh.tmp
mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh
'';
system.activationScripts.binsh = lib.stringAfter [ "stdio" ] ''
# Create the required /bin/sh symlink; otherwise lots of things
# (notably the system() function) won't work.
mkdir -p /bin
chmod 0755 /bin
ln -sfn "${cfg.binsh}" /bin/.sh.tmp
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
inherit (lib) mkIf mkOption types;
randomEncryptionCoerce = enable: { inherit enable; };
randomEncryptionOpts = { ... }: {
randomEncryptionOpts =
{ ... }:
{
options = {
options = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Encrypt swap device with a random key. This way you won't have a persistent swap device.
enable = mkOption {
default = false;
type = types.bool;
description = ''
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
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: 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/
'';
};
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 {manpage}`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 {manpage}`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";
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/
'';
};
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
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!
Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
'';
};
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/
'';
};
keySize = mkOption {
default = null;
example = "512";
type = types.nullOr types.int;
description = ''
Set the encryption key size for the plain device.
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 {manpage}`swapon(8)` for more information.
'';
};
If not specified, the amount of data to read from `source` will be
determined by cryptsetup.
options = mkOption {
default = [ "defaults" ];
example = [ "nofail" ];
type = types.listOf types.nonEmptyStr;
description = ''
Options used to mount the swap.
'';
};
See {manpage}`cryptsetup-open(8)` for details.
'';
};
deviceName = mkOption {
type = types.str;
internal = true;
};
sectorSize = mkOption {
default = null;
example = "4096";
type = types.nullOr types.int;
description = ''
Set the sector size for the plain encrypted device type.
realDevice = mkOption {
type = types.path;
internal = true;
If not specified, the default sector size is determined from the
underlying block device.
See {manpage}`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 = {
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;
};
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.
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 {manpage}`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
@ -201,7 +217,7 @@ in
options = {
swapDevices = mkOption {
default = [];
default = [ ];
example = [
{ device = "/dev/hda7"; }
{ device = "/var/swapfile"; }
@ -224,7 +240,8 @@ in
config = mkIf ((lib.length config.swapDevices) != 0) {
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 = ''
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.
@ -232,12 +249,13 @@ in
'';
}) config.swapDevices;
warnings =
lib.concatMap (sw:
if sw.size != null && lib.hasPrefix "/dev/" sw.device
then [ "Setting the swap size of block device ${sw.device} has no effect" ]
else [ ])
config.swapDevices;
warnings = lib.concatMap (
sw:
if sw.size != null && lib.hasPrefix "/dev/" sw.device then
[ "Setting the swap size of block device ${sw.device} has no effect" ]
else
[ ]
) config.swapDevices;
system.requiredKernelConfig = [
(config.lib.kernelConfig.isYes "SWAP")
@ -246,47 +264,62 @@ in
# Create missing swapfiles.
systemd.services =
let
createSwapDevice = sw:
let realDevice' = utils.escapeSystemdPath sw.realDevice;
in lib.nameValuePair "mkswap-${sw.deviceName}"
{ description = "Initialisation of swap device ${sw.device}";
createSwapDevice =
sw:
let
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
# loop module has not been loaded before the service runs.
# We add an ordering constraint to run after systemd-modules-load to
# avoid this race condition.
after = [ "systemd-modules-load.service" ];
wantedBy = [ "${realDevice'}.swap" ];
before = [ "${realDevice'}.swap" "shutdown.target"];
before = [
"${realDevice'}.swap"
"shutdown.target"
];
conflicts = [ "shutdown.target" ];
path = [ pkgs.util-linux pkgs.e2fsprogs ]
++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup;
path = [
pkgs.util-linux
pkgs.e2fsprogs
] ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup;
environment.DEVICE = sw.device;
script =
''
${lib.optionalString (sw.size != null) ''
currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
# Disable CoW for CoW based filesystems like BTRFS.
truncate --size 0 "$DEVICE"
chattr +C "$DEVICE" 2>/dev/null || true
script = ''
${lib.optionalString (sw.size != null) ''
currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
# Disable CoW for CoW based filesystems like BTRFS.
truncate --size 0 "$DEVICE"
chattr +C "$DEVICE" 2>/dev/null || true
echo "Creating swap file using dd and mkswap."
dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress
${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
fi
''}
${lib.optionalString sw.randomEncryption.enable ''
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.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}
''}
'';
echo "Creating swap file using dd and mkswap."
dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress
${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
fi
''}
${lib.optionalString sw.randomEncryption.enable ''
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.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.DefaultDependencies = false; # needed to prevent a cycle
@ -299,7 +332,12 @@ in
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
# initialised by pam_env (that is, not only in shells).
{ config, lib, options, pkgs, ... }:
{
config,
lib,
options,
pkgs,
...
}:
let
cfg = config.environment;
@ -12,7 +18,7 @@ in
options = {
environment.sessionVariables = lib.mkOption {
default = {};
default = { };
description = ''
A set of environment variables used in the global environment.
These variables will be set by PAM early in the login process.
@ -37,7 +43,13 @@ in
environment.profileRelativeSessionVariables = lib.mkOption {
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 = ''
Attribute set of environment variable used in the global
environment. These variables will be set by PAM early in the
@ -61,40 +73,40 @@ in
};
config = {
environment.etc."pam/environment".text = let
suffixedVariables =
lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (envVar: suffixes:
lib.flip lib.concatMap cfg.profiles (profile:
map (suffix: "${profile}${suffix}") suffixes
)
environment.etc."pam/environment".text =
let
suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeSessionVariables (
envVar: suffixes:
lib.flip lib.concatMap cfg.profiles (profile: map (suffix: "${profile}${suffix}") suffixes)
);
# We're trying to use the same syntax for PAM variables and env variables.
# That means we need to map the env variables that people might use to their
# equivalent PAM variable.
replaceEnvVars = lib.replaceStrings ["$HOME" "$USER"] ["@{HOME}" "@{PAM_USER}"];
# We're trying to use the same syntax for PAM variables and env variables.
# That means we need to map the env variables that people might use to their
# equivalent PAM variable.
replaceEnvVars = lib.replaceStrings [ "$HOME" "$USER" ] [ "@{HOME}" "@{PAM_USER}" ];
pamVariable = n: v:
''${n} DEFAULT="${lib.concatStringsSep ":" (map replaceEnvVars (lib.toList v))}"'';
pamVariable =
n: v: ''${n} DEFAULT="${lib.concatStringsSep ":" (map replaceEnvVars (lib.toList v))}"'';
pamVariables =
lib.concatStringsSep "\n"
(lib.mapAttrsToList pamVariable
(lib.zipAttrsWith (n: lib.concatLists)
[
# Make sure security wrappers are prioritized without polluting
# shell environments with an extra entry. Sessions which depend on
# pam for its environment will otherwise have eg. broken sudo. In
# particular Gnome Shell sometimes fails to source a proper
# environment from a shell.
{ PATH = [ config.security.wrapperDir ]; }
pamVariables = lib.concatStringsSep "\n" (
lib.mapAttrsToList pamVariable (
lib.zipAttrsWith (n: lib.concatLists) [
# Make sure security wrappers are prioritized without polluting
# shell environments with an extra entry. Sessions which depend on
# pam for its environment will otherwise have eg. broken sudo. In
# particular Gnome Shell sometimes fails to source a proper
# environment from a shell.
{ PATH = [ config.security.wrapperDir ]; }
(lib.mapAttrs (n: lib.toList) cfg.sessionVariables)
suffixedVariables
]));
in ''
${pamVariables}
'';
(lib.mapAttrs (n: lib.toList) cfg.sessionVariables)
suffixedVariables
]
)
);
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
cfg = config.xdg.mime;
associationOptions = with lib.types; attrsOf (
coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str
);
associationOptions =
with lib.types;
attrsOf (coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str);
in
{
@ -24,10 +29,13 @@ in
xdg.mime.addedAssociations = lib.mkOption {
type = associationOptions;
default = {};
default = { };
example = {
"application/pdf" = "firefox.desktop";
"text/xml" = [ "nvim.desktop" "codium.desktop" ];
"text/xml" = [
"nvim.desktop"
"codium.desktop"
];
};
description = ''
Adds associations between mimetypes and applications. See the
@ -38,10 +46,13 @@ in
xdg.mime.defaultApplications = lib.mkOption {
type = associationOptions;
default = {};
default = { };
example = {
"application/pdf" = "firefox.desktop";
"image/png" = [ "sxiv.desktop" "gimp.desktop" ];
"image/png" = [
"sxiv.desktop"
"gimp.desktop"
];
};
description = ''
Sets the default applications for given mimetypes. See the
@ -52,9 +63,12 @@ in
xdg.mime.removedAssociations = lib.mkOption {
type = associationOptions;
default = {};
default = { };
example = {
"audio/mp3" = [ "mpv.desktop" "umpv.desktop" ];
"audio/mp3" = [
"mpv.desktop"
"umpv.desktop"
];
"inode/directory" = "codium.desktop";
};
description = ''
@ -66,17 +80,16 @@ in
};
config = lib.mkIf cfg.enable {
environment.etc."xdg/mimeapps.list" = lib.mkIf (
cfg.addedAssociations != {}
|| cfg.defaultApplications != {}
|| cfg.removedAssociations != {}
) {
text = lib.generators.toINI { } {
"Added Associations" = cfg.addedAssociations;
"Default Applications" = cfg.defaultApplications;
"Removed Associations" = cfg.removedAssociations;
};
};
environment.etc."xdg/mimeapps.list" =
lib.mkIf
(cfg.addedAssociations != { } || cfg.defaultApplications != { } || cfg.removedAssociations != { })
{
text = lib.generators.toINI { } {
"Added Associations" = cfg.addedAssociations;
"Default Applications" = cfg.defaultApplications;
"Removed Associations" = cfg.removedAssociations;
};
};
environment.pathsToLink = [ "/share/mime" ];

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
@ -10,7 +15,10 @@ in
{
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
@ -73,7 +81,16 @@ in
algorithm = lib.mkOption {
default = "zstd";
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 = ''
Compression algorithm. `lzo` has good compression,
but is slow. `lz4` has bad compression, but is fast.
@ -107,23 +124,24 @@ in
services.zram-generator.enable = true;
services.zram-generator.settings = lib.listToAttrs
(builtins.map
(dev: {
name = dev;
value =
let
size = "${toString cfg.memoryPercent} / 100 * ram";
in
{
zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
compression-algorithm = cfg.algorithm;
swap-priority = cfg.priority;
} // lib.optionalAttrs (cfg.writebackDevice != null) {
writeback-device = cfg.writebackDevice;
};
})
devices);
services.zram-generator.settings = lib.listToAttrs (
builtins.map (dev: {
name = dev;
value =
let
size = "${toString cfg.memoryPercent} / 100 * ram";
in
{
zram-size =
if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
compression-algorithm = cfg.algorithm;
swap-priority = cfg.priority;
}
// lib.optionalAttrs (cfg.writebackDevice != null) {
writeback-device = cfg.writebackDevice;
};
}) devices
);
};

View file

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

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.graphics;
@ -14,16 +19,35 @@ let
in
{
imports = [
(lib.mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "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
[ "services" "xserver" "vaapiDrivers" ]
[ "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" "driSupport32Bit"] [ "hardware" "graphics" "enable32Bit" ])
(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" ])
(lib.mkRenamedOptionModule [ "hardware" "opengl" "enable" ] [ "hardware" "graphics" "enable" ])
(lib.mkRenamedOptionModule
[ "hardware" "opengl" "driSupport32Bit" ]
[ "hardware" "graphics" "enable32Bit" ]
)
(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 = {
@ -78,7 +102,7 @@ in
:::
'';
type = lib.types.listOf lib.types.package;
default = [];
default = [ ];
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;
default = [];
default = [ ];
example = lib.literalExpression "with pkgs.pkgsi686Linux; [ intel-media-driver intel-vaapi-driver ]";
};
};
@ -117,7 +141,7 @@ in
else if cfg.enable32Bit then
{ "L+".argument = toString driversEnv32; }
else
{ "r" = {}; };
{ "r" = { }; };
};
hardware.graphics.package = lib.mkDefault pkgs.mesa;

View file

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

View file

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

View file

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

View file

@ -1,82 +1,108 @@
# This is an expression meant to be called from `./repart.nix`, it is NOT a
# NixOS module that can be imported.
{ lib
, stdenvNoCC
, runCommand
, python3
, black
, ruff
, mypy
, systemd
, fakeroot
, util-linux
{
lib,
stdenvNoCC,
runCommand,
python3,
black,
ruff,
mypy,
systemd,
fakeroot,
util-linux,
# filesystem tools
, dosfstools
, mtools
, e2fsprogs
, squashfsTools
, erofs-utils
, btrfs-progs
, xfsprogs
dosfstools,
mtools,
e2fsprogs,
squashfsTools,
erofs-utils,
btrfs-progs,
xfsprogs,
# compression tools
, zstd
, xz
, zeekstd
zstd,
xz,
zeekstd,
# arguments
, name
, version
, imageFileBasename
, compression
, fileSystems
, finalPartitions
, split
, seed
, definitionsDirectory
, sectorSize
, mkfsEnv ? {}
, createEmpty ? true
name,
version,
imageFileBasename,
compression,
fileSystems,
finalPartitions,
split,
seed,
definitionsDirectory,
sectorSize,
mkfsEnv ? { },
createEmpty ? true,
}:
let
systemdArch = let
inherit (stdenvNoCC) hostPlatform;
in
if hostPlatform.isAarch32 then "arm"
else if hostPlatform.isAarch64 then "arm64"
else if hostPlatform.isx86_32 then "x86"
else if hostPlatform.isx86_64 then "x86-64"
else if hostPlatform.isMips32 then "mips-le"
else if hostPlatform.isMips64 then "mips64-le"
else if hostPlatform.isPower then "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;
systemdArch =
let
inherit (stdenvNoCC) hostPlatform;
in
if hostPlatform.isAarch32 then
"arm"
else if hostPlatform.isAarch64 then
"arm64"
else if hostPlatform.isx86_32 then
"x86"
else if hostPlatform.isx86_64 then
"x86-64"
else if hostPlatform.isMips32 then
"mips-le"
else if hostPlatform.isMips64 then
"mips64-le"
else if hostPlatform.isPower then
"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"
{
# TODO: ruff does not splice properly in nativeBuildInputs
depsBuildBuild = [ ruff ];
nativeBuildInputs = [ python3 black mypy ];
} ''
install ${./amend-repart-definitions.py} $out
patchShebangs --build $out
amendRepartDefinitions =
runCommand "amend-repart-definitions.py"
{
# TODO: ruff does not splice properly in nativeBuildInputs
depsBuildBuild = [ ruff ];
nativeBuildInputs = [
python3
black
mypy
];
}
''
install ${./amend-repart-definitions.py} $out
patchShebangs --build $out
black --check --diff $out
ruff check --line-length 88 $out
mypy --strict $out
'';
black --check --diff $out
ruff check --line-length 88 $out
mypy --strict $out
'';
fileSystemToolMapping = {
"vfat" = [ dosfstools mtools ];
"vfat" = [
dosfstools
mtools
];
"ext4" = [ e2fsprogs.bin ];
"squashfs" = [ squashfsTools ];
"erofs" = [ erofs-utils ];
@ -87,108 +113,127 @@ let
fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
compressionPkg = {
"zstd" = zstd;
"xz" = xz;
"zstd-seekable" = zeekstd;
}."${compression.algorithm}";
compressionPkg =
{
"zstd" = zstd;
"xz" = xz;
"zstd-seekable" = zeekstd;
}
."${compression.algorithm}";
compressionCommand = {
"zstd" = "zstd --no-progress --threads=$NIX_BUILD_CORES -${toString compression.level}";
"xz" = "xz --keep --verbose --threads=$NIX_BUILD_CORES -${toString compression.level}";
"zstd-seekable" = "zeekstd --quiet --max-frame-size 2M --compression-level ${toString compression.level}";
}."${compression.algorithm}";
compressionCommand =
{
"zstd" = "zstd --no-progress --threads=$NIX_BUILD_CORES -${toString compression.level}";
"xz" = "xz --keep --verbose --threads=$NIX_BUILD_CORES -${toString compression.level}";
"zstd-seekable" =
"zeekstd --quiet --max-frame-size 2M --compression-level ${toString compression.level}";
}
."${compression.algorithm}";
in
stdenvNoCC.mkDerivation (finalAttrs:
(if (version != null)
then { pname = name; inherit version; }
else { inherit name; }
) // {
__structuredAttrs = true;
stdenvNoCC.mkDerivation (
finalAttrs:
(
if (version != null) then
{
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
# to the closure that was used to build it
unsafeDiscardReferences.out = true;
nativeBuildInputs =
[
systemd
util-linux
fakeroot
]
++ lib.optionals (compression.enable) [
compressionPkg
]
++ fileSystemTools;
nativeBuildInputs = [
systemd
util-linux
fakeroot
] ++ lib.optionals (compression.enable) [
compressionPkg
] ++ fileSystemTools;
env = mkfsEnv;
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
finalRepartDefinitions = "repart.d";
systemdRepartFlags =
[
"--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 = [
"--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}"
];
dontUnpack = true;
dontConfigure = true;
doCheck = false;
dontUnpack = true;
dontConfigure = true;
doCheck = false;
patchPhase = ''
runHook prePatch
patchPhase = ''
runHook prePatch
amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory)
ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory)
ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
runHook postPatch
'';
runHook postPatch
'';
buildPhase = ''
runHook preBuild
buildPhase = ''
runHook preBuild
echo "Building image with systemd-repart..."
unshare --map-root-user fakeroot systemd-repart \
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
echo "Building image with systemd-repart..."
unshare --map-root-user fakeroot systemd-repart \
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
runHook postBuild
'';
runHook postBuild
'';
installPhase =
''
runHook preInstall
installPhase = ''
runHook preInstall
mkdir -p $out
''
# 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
''
# 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
'';
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
# (GPT). It uses systemd-repart to build the image.
{ config, pkgs, lib, utils, ... }:
{
config,
pkgs,
lib,
utils,
...
}:
let
cfg = config.image.repart;
@ -27,14 +33,16 @@ let
};
contents = lib.mkOption {
type = with lib.types; attrsOf (submodule {
options = {
source = lib.mkOption {
type = types.path;
description = "Path of the source file.";
type =
with lib.types;
attrsOf (submodule {
options = {
source = lib.mkOption {
type = types.path;
description = "Path of the source file.";
};
};
};
});
});
default = { };
example = lib.literalExpression ''
{
@ -48,7 +56,14 @@ let
};
repartConfig = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ str int bool (listOf str) ]);
type =
with lib.types;
attrsOf (oneOf [
str
int
bool
(listOf str)
]);
example = {
Type = "home";
SizeMinBytes = "512M";
@ -63,10 +78,12 @@ let
};
};
mkfsOptionsToEnv = opts: lib.mapAttrs' (fsType: options: {
name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
value = builtins.concatStringsSep " " options;
}) opts;
mkfsOptionsToEnv =
opts:
lib.mapAttrs' (fsType: options: {
name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
value = builtins.concatStringsSep " " options;
}) opts;
in
{
imports = [
@ -113,7 +130,11 @@ in
enable = lib.mkEnableOption "Image compression";
algorithm = lib.mkOption {
type = lib.types.enum [ "zstd" "xz" "zstd-seekable" ];
type = lib.types.enum [
"zstd"
"xz"
"zstd-seekable"
];
default = "zstd";
description = "Compression algorithm";
};
@ -159,7 +180,10 @@ in
package = lib.mkPackageOption pkgs "systemd-repart" {
# We use buildPackages so that repart images are built with the build
# platform's systemd, allowing for cross-compiled systems to work.
default = [ "buildPackages" "systemd" ];
default = [
"buildPackages"
"systemd"
];
example = "pkgs.buildPackages.systemdMinimal.override { withCryptsetup = true; }";
};
@ -196,7 +220,7 @@ in
mkfsOptions = lib.mkOption {
type = with lib.types; attrsOf (listOf str);
default = {};
default = { };
example = lib.literalExpression ''
{
vfat = [ "-S 512" "-c" ];
@ -230,7 +254,8 @@ in
config = {
assertions = lib.mapAttrsToList (fileName: partitionConfig:
assertions = lib.mapAttrsToList (
fileName: partitionConfig:
let
inherit (partitionConfig) repartConfig;
labelLength = builtins.stringLength repartConfig.Label;
@ -240,52 +265,59 @@ in
message = ''
The partition label '${repartConfig.Label}'
defined for '${fileName}' is ${toString labelLength} characters long,
but the maximum label length supported by UEFI is ${toString
GPTMaxLabelLength}.
but the maximum label length supported by UEFI is ${toString GPTMaxLabelLength}.
'';
}
) cfg.partitions;
warnings = lib.filter (v: v != null) (lib.mapAttrsToList (fileName: partitionConfig:
let
inherit (partitionConfig) repartConfig;
suggestedMaxLabelLength = GPTMaxLabelLength - 2;
labelLength = builtins.stringLength repartConfig.Label;
in
if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then ''
The partition label '${repartConfig.Label}'
defined for '${fileName}' is ${toString labelLength} characters long.
The suggested maximum label length is ${toString
suggestedMaxLabelLength}.
warnings = lib.filter (v: v != null) (
lib.mapAttrsToList (
fileName: partitionConfig:
let
inherit (partitionConfig) repartConfig;
suggestedMaxLabelLength = GPTMaxLabelLength - 2;
labelLength = builtins.stringLength repartConfig.Label;
in
if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
''
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
not leave enough space to increment the version number included in
the label in a future release. For example, if your label is
${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
you're at version 9, you cannot increment this to 10.
'' else null
) cfg.partitions);
If you use sytemd-sysupdate style A/B updates, this might
not leave enough space to increment the version number included in
the label in a future release. For example, if your label is
${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
you're at version 9, you cannot increment this to 10.
''
else
null
) cfg.partitions
);
image.repart =
let
version = config.image.repart.version;
versionInfix = if version != null then "_${version}" else "";
compressionSuffix = lib.optionalString cfg.compression.enable
{
"zstd" = ".zst";
"xz" = ".xz";
"zstd-seekable" = ".zst";
}."${cfg.compression.algorithm}";
compressionSuffix =
lib.optionalString cfg.compression.enable
{
"zstd" = ".zst";
"xz" = ".xz";
"zstd-seekable" = ".zst";
}
."${cfg.compression.algorithm}";
makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
# Add the closure of the provided Nix store paths to cfg.partitions so
# that amend-repart-definitions.py can read it.
addClosure = _name: partitionConfig: partitionConfig // (
lib.optionalAttrs
(partitionConfig.storePaths or [ ] != [ ])
{ closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
);
addClosure =
_name: partitionConfig:
partitionConfig
// (lib.optionalAttrs (partitionConfig.storePaths or [ ] != [ ]) {
closure = "${makeClosure partitionConfig.storePaths}/store-paths";
});
in
{
name = lib.mkIf (config.system.image.id != null) (lib.mkOptionDefault config.system.image.id);
@ -296,11 +328,14 @@ in
# Generally default to slightly faster than default compression
# levels under the assumption that most of the building will be done
# for development and release builds will be customized.
level = lib.mkOptionDefault {
"zstd" = 3;
"xz" = 3;
"zstd-seekable" = 3;
}."${cfg.compression.algorithm}";
level =
lib.mkOptionDefault
{
"zstd" = 3;
"xz" = 3;
"zstd-seekable" = 3;
}
."${cfg.compression.algorithm}";
};
finalPartitions = lib.mapAttrs addClosure cfg.partitions;
@ -308,27 +343,37 @@ in
system.build.image =
let
fileSystems = lib.filter
(f: f != null)
(lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
fileSystems = lib.filter (f: f != null) (
lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions
);
format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
definitionsDirectory = utils.systemdUtils.lib.definitions
"repart.d"
format
(lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions);
definitionsDirectory = utils.systemdUtils.lib.definitions "repart.d" format (
lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions
);
mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
in
pkgs.callPackage ./repart-image.nix {
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;
};
meta.maintainers = with lib.maintainers; [ nikstur willibutz ];
meta.maintainers = with lib.maintainers; [
nikstur
willibutz
];
};
}

View file

@ -1,14 +1,20 @@
# This module contains the basic configuration for building a NixOS
# installation CD.
{ config, lib, options, pkgs, ... }:
{
imports =
[ ./iso-image.nix
config,
lib,
options,
pkgs,
...
}:
{
imports = [
./iso-image.nix
# Profiles of this basic installation CD.
../../profiles/base.nix
../../profiles/installation-device.nix
];
# Profiles of this basic installation CD.
../../profiles/base.nix
../../profiles/installation-device.nix
];
hardware.enableAllHardware = true;

View file

@ -1,7 +1,13 @@
# This module creates netboot media containing the given NixOS
# configuration.
{ config, lib, pkgs, modulesPath, ... }:
{
config,
lib,
pkgs,
modulesPath,
...
}:
with lib;
@ -36,43 +42,50 @@ with lib;
# here and it causes a cyclic dependency.
boot.loader.grub.enable = false;
fileSystems."/" = mkImageMediaOverride
{ fsType = "tmpfs";
options = [ "mode=0755" ];
};
fileSystems."/" = mkImageMediaOverride {
fsType = "tmpfs";
options = [ "mode=0755" ];
};
# In stage 1, mount a tmpfs on top of /nix/store (the squashfs
# image) to make this a live CD.
fileSystems."/nix/.ro-store" = mkImageMediaOverride
{ fsType = "squashfs";
device = "../nix-store.squashfs";
options = [ "loop" ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
neededForBoot = true;
fileSystems."/nix/.ro-store" = mkImageMediaOverride {
fsType = "squashfs";
device = "../nix-store.squashfs";
options = [
"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
{ fsType = "tmpfs";
options = [ "mode=0755" ];
neededForBoot = true;
};
boot.initrd.availableKernelModules = [
"squashfs"
"overlay"
];
fileSystems."/nix/store" = mkImageMediaOverride
{ overlay = {
lowerdir = [ "/nix/.ro-store" ];
upperdir = "/nix/.rw-store/store";
workdir = "/nix/.rw-store/work";
};
neededForBoot = true;
};
boot.initrd.availableKernelModules = [ "squashfs" "overlay" ];
boot.initrd.kernelModules = [ "loop" "overlay" ];
boot.initrd.kernelModules = [
"loop"
"overlay"
];
# Closures to be copied to the Nix store, namely the init
# script and the top-level system configuration directory.
netboot.storeContents =
[ config.system.build.toplevel ];
netboot.storeContents = [ config.system.build.toplevel ];
# Create the squashfs image that contains the Nix store.
system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
@ -80,17 +93,17 @@ with lib;
comp = config.netboot.squashfsCompression;
};
# Create the initrd
system.build.netbootRamdisk = pkgs.makeInitrdNG {
inherit (config.boot.initrd) compressor;
prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
contents =
[ { source = config.system.build.squashfsStore;
target = "/nix-store.squashfs";
}
];
contents = [
{
source = config.system.build.squashfsStore;
target = "/nix-store.squashfs";
}
];
};
system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
@ -137,16 +150,18 @@ with lib;
image.filePath = "tarball/${config.image.fileName}";
system.nixos.tags = [ "kexec" ];
system.build.image = config.system.build.kexecTarball;
system.build.kexecTarball = pkgs.callPackage "${toString modulesPath}/../lib/make-system-tarball.nix" {
fileName = config.image.baseName;
storeContents = [
system.build.kexecTarball =
pkgs.callPackage "${toString modulesPath}/../lib/make-system-tarball.nix"
{
object = config.system.build.kexecScript;
symlink = "/kexec_nixos";
}
];
contents = [];
};
fileName = config.image.baseName;
storeContents = [
{
object = config.system.build.kexecScript;
symlink = "/kexec_nixos";
}
];
contents = [ ];
};
boot.loader.timeout = 10;

View file

@ -1,6 +1,11 @@
# To build, use:
# 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 = [
@ -16,52 +21,58 @@
# The serial ports listed here are:
# - ttyS0: for Tegra (Jetson TX1)
# - 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 = {
populateFirmwareCommands = let
configTxt = pkgs.writeText "config.txt" ''
[pi3]
kernel=u-boot-rpi3.bin
populateFirmwareCommands =
let
configTxt = pkgs.writeText "config.txt" ''
[pi3]
kernel=u-boot-rpi3.bin
# Otherwise the serial output will be garbled.
core_freq=250
# Otherwise the serial output will be garbled.
core_freq=250
[pi02]
kernel=u-boot-rpi3.bin
[pi02]
kernel=u-boot-rpi3.bin
[pi4]
kernel=u-boot-rpi4.bin
enable_gic=1
armstub=armstub8-gic.bin
[pi4]
kernel=u-boot-rpi4.bin
enable_gic=1
armstub=armstub8-gic.bin
# Otherwise the resolution will be weird in most cases, compared to
# what the pi3 firmware does by default.
disable_overscan=1
# Otherwise the resolution will be weird in most cases, compared to
# what the pi3 firmware does by default.
disable_overscan=1
# Supported in newer board revisions
arm_boost=1
# Supported in newer board revisions
arm_boost=1
[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# 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.
otg_mode=1
[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# 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.
otg_mode=1
[all]
# Boot in 64-bit mode.
arm_64bit=1
[all]
# Boot in 64-bit mode.
arm_64bit=1
# 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
# a requirement in the future.
enable_uart=1
# 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
# a requirement in the future.
enable_uart=1
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
# when attempting to show low-voltage or overtemperature warnings.
avoid_warnings=1
'';
in ''
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
# when attempting to show low-voltage or overtemperature warnings.
avoid_warnings=1
'';
in
''
(cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
# Add the config

View file

@ -11,24 +11,36 @@
# The derivation for the SD image will be placed in
# config.system.build.sdImage
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
let
rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
inherit (config.sdImage) storePaths;
compressImage = config.sdImage.compressImage;
populateImageCommands = config.sdImage.populateRootCommands;
volumeLabel = "NIXOS_SD";
} // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
uuid = config.sdImage.rootPartitionUUID;
});
rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix (
{
inherit (config.sdImage) storePaths;
compressImage = config.sdImage.compressImage;
populateImageCommands = config.sdImage.populateRootCommands;
volumeLabel = "NIXOS_SD";
}
// optionalAttrs (config.sdImage.rootPartitionUUID != null) {
uuid = config.sdImage.rootPartitionUUID;
}
);
in
{
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" "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.")
(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" "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."
)
(lib.mkRenamedOptionModuleWith {
sinceRelease = 2505;
from = [
@ -180,7 +192,10 @@ in
# Alternatively, this could be removed from the configuration.
# The filesystem is not needed at runtime, it could be treated
# as an opaque blob instead of a discrete FAT32 filesystem.
options = [ "nofail" "noauto" ];
options = [
"nofail"
"noauto"
];
};
"/" = {
device = "/dev/disk/by-label/NIXOS_SD";
@ -194,122 +209,139 @@ in
image.filePath = "sd-card/${config.image.fileName}";
system.nixos.tags = [ "sd-card" ];
system.build.image = config.system.build.sdImage;
system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs,
mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation {
name = config.image.fileName;
system.build.sdImage = pkgs.callPackage (
{
stdenv,
dosfstools,
e2fsprogs,
mtools,
libfaketime,
util-linux,
zstd,
}:
stdenv.mkDerivation {
name = config.image.fileName;
nativeBuildInputs = [ dosfstools e2fsprogs libfaketime mtools util-linux ]
++ lib.optional config.sdImage.compressImage zstd;
nativeBuildInputs = [
dosfstools
e2fsprogs
libfaketime
mtools
util-linux
] ++ lib.optional config.sdImage.compressImage zstd;
inherit (config.sdImage) compressImage;
inherit (config.sdImage) compressImage;
buildCommand = ''
mkdir -p $out/nix-support $out/sd-image
export img=$out/sd-image/${config.image.baseName}.img
buildCommand = ''
mkdir -p $out/nix-support $out/sd-image
export img=$out/sd-image/${config.image.baseName}.img
echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
if test -n "$compressImage"; then
echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products
else
echo "file sd-image $img" >> $out/nix-support/hydra-build-products
fi
echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
if test -n "$compressImage"; then
echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products
else
echo "file sd-image $img" >> $out/nix-support/hydra-build-products
fi
root_fs=${rootfsImage}
${lib.optionalString config.sdImage.compressImage ''
root_fs=./root-fs.img
echo "Decompressing rootfs image"
zstd -d --no-progress "${rootfsImage}" -o $root_fs
''}
root_fs=${rootfsImage}
${lib.optionalString config.sdImage.compressImage ''
root_fs=./root-fs.img
echo "Decompressing rootfs image"
zstd -d --no-progress "${rootfsImage}" -o $root_fs
''}
# Gap in front of the first partition, in MiB
gap=${toString config.sdImage.firmwarePartitionOffset}
# Gap in front of the first partition, in MiB
gap=${toString config.sdImage.firmwarePartitionOffset}
# 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 }')
firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
truncate -s $imageSize $img
# 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 }')
firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
truncate -s $imageSize $img
# type=b is 'W95 FAT32', type=83 is 'Linux'.
# The "bootable" partition is where u-boot will look file for the bootloader
# information (dtbs, extlinux.conf file).
sfdisk --no-reread --no-tell-kernel $img <<EOF
label: dos
label-id: ${config.sdImage.firmwarePartitionID}
# type=b is 'W95 FAT32', type=83 is 'Linux'.
# The "bootable" partition is where u-boot will look file for the bootloader
# information (dtbs, extlinux.conf file).
sfdisk --no-reread --no-tell-kernel $img <<EOF
label: dos
label-id: ${config.sdImage.firmwarePartitionID}
start=''${gap}M, size=$firmwareSizeBlocks, type=b
start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
EOF
start=''${gap}M, size=$firmwareSizeBlocks, type=b
start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
EOF
# Copy the rootfs into the SD image
eval $(partx $img -o START,SECTORS --nr 2 --pairs)
dd conv=notrunc if=$root_fs of=$img seek=$START count=$SECTORS
# Copy the rootfs into the SD image
eval $(partx $img -o START,SECTORS --nr 2 --pairs)
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
eval $(partx $img -o START,SECTORS --nr 1 --pairs)
truncate -s $((SECTORS * 512)) 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)
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
mkdir firmware
${config.sdImage.populateFirmwareCommands}
# Populate the files intended for /boot/firmware
mkdir firmware
${config.sdImage.populateFirmwareCommands}
find firmware -exec touch --date=2000-01-01 {} +
# Copy the populated /boot/firmware into the SD image
cd firmware
# Force a fixed order in mcopy for better determinism, and avoid file globbing
for d in $(find . -type d -mindepth 1 | sort); do
faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d"
done
for f in $(find . -type f | sort); do
mcopy -pvm -i ../firmware_part.img "$f" "::/$f"
done
cd ..
find firmware -exec touch --date=2000-01-01 {} +
# Copy the populated /boot/firmware into the SD image
cd firmware
# Force a fixed order in mcopy for better determinism, and avoid file globbing
for d in $(find . -type d -mindepth 1 | sort); do
faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d"
done
for f in $(find . -type f | sort); do
mcopy -pvm -i ../firmware_part.img "$f" "::/$f"
done
cd ..
# Verify the FAT partition before copying it.
fsck.vfat -vn firmware_part.img
dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
# Verify the FAT partition before copying it.
fsck.vfat -vn firmware_part.img
dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
${config.sdImage.postBuildCommands}
${config.sdImage.postBuildCommands}
if test -n "$compressImage"; then
zstd -T$NIX_BUILD_CORES --rm $img
if test -n "$compressImage"; then
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
'';
}) {};
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,4 +1,16 @@
{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, specialArgs, ... }:
{
config,
options,
lib,
pkgs,
utils,
modules,
baseModules,
extraModules,
modulesPath,
specialArgs,
...
}:
let
inherit (lib)
@ -31,25 +43,26 @@ let
cfg = config.documentation;
allOpts = options;
canCacheDocs = m:
canCacheDocs =
m:
let
f = import m;
instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f));
in
cfg.nixos.options.splitBuild
&& isPath m
&& isFunction f
&& instance ? options
&& instance.meta.buildDocsInSandbox or true;
cfg.nixos.options.splitBuild
&& isPath m
&& isFunction f
&& instance ? options
&& instance.meta.buildDocsInSandbox or true;
docModules =
let
p = partition canCacheDocs (baseModules ++ cfg.nixos.extraModules);
in
{
lazy = p.right;
eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
};
{
lazy = p.right;
eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
};
manual = import ../../doc/manual rec {
inherit pkgs config;
@ -59,9 +72,11 @@ let
options =
let
scrubbedEval = evalModules {
modules = [ {
_module.check = false;
} ] ++ docModules.eager;
modules = [
{
_module.check = false;
}
] ++ docModules.eager;
class = "nixos";
specialArgs = specialArgs // {
pkgs = scrubDerivations "pkgs" pkgs;
@ -71,33 +86,37 @@ let
inherit modulesPath utils;
};
};
scrubDerivations = namePrefix: pkgSet: mapAttrs
(name: value:
scrubDerivations =
namePrefix: pkgSet:
mapAttrs (
name: value:
let
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.";
in if isAttrs value then
in
if isAttrs value then
scrubDerivations wholeName value
// optionalAttrs (isDerivation value) {
outPath = guard "\${${wholeName}}";
drvPath = guard value.drvPath;
}
else value
)
pkgSet;
in scrubbedEval.options;
else
value
) pkgSet;
in
scrubbedEval.options;
baseOptionsJSON =
let
filter =
builtins.filterSource
(n: t:
cleanSourceFilter n t
&& (t == "directory" -> baseNameOf n != "tests")
&& (t == "file" -> hasSuffix ".nix" n)
);
filter = builtins.filterSource (
n: t:
cleanSourceFilter n t
&& (t == "directory" -> baseNameOf n != "tests")
&& (t == "file" -> hasSuffix ".nix" n)
);
in
pkgs.runCommand "lazy-options.json" {
pkgs.runCommand "lazy-options.json"
{
libPath = filter (pkgs.path + "/lib");
pkgsLibPath = filter (pkgs.path + "/pkgs/pkgs-lib");
nixosPath = filter (pkgs.path + "/nixos");
@ -107,7 +126,8 @@ let
+ concatMapStringsSep " " (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy
+ " ]";
passAsFile = [ "modules" ];
} ''
}
''
export NIX_STORE_DIR=$TMPDIR/store
export NIX_STATE_DIR=$TMPDIR/state
${pkgs.buildPackages.nix}/bin/nix-instantiate \
@ -139,36 +159,37 @@ let
inherit (cfg.nixos.options) warningsAreErrors;
};
nixos-help = let
helpScript = pkgs.writeShellScriptBin "nixos-help" ''
# Finds first executable browser in a colon-separated list.
# (see how xdg-open defines BROWSER)
browser="$(
IFS=: ; for b in $BROWSER; do
[ -n "$(type -P "$b" || true)" ] && echo "$b" && break
done
)"
if [ -z "$browser" ]; then
browser="$(type -P xdg-open || true)"
nixos-help =
let
helpScript = pkgs.writeShellScriptBin "nixos-help" ''
# Finds first executable browser in a colon-separated list.
# (see how xdg-open defines BROWSER)
browser="$(
IFS=: ; for b in $BROWSER; do
[ -n "$(type -P "$b" || true)" ] && echo "$b" && break
done
)"
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
exec "$browser" ${manual.manualHTMLIndex}
'';
exec "$browser" ${manual.manualHTMLIndex}
'';
desktopItem = pkgs.makeDesktopItem {
name = "nixos-manual";
desktopName = "NixOS Manual";
genericName = "System Manual";
comment = "View NixOS documentation in a web browser";
icon = "nix-snowflake";
exec = "nixos-help";
categories = ["System"];
};
desktopItem = pkgs.makeDesktopItem {
name = "nixos-manual";
desktopName = "NixOS Manual";
genericName = "System Manual";
comment = "View NixOS documentation in a web browser";
icon = "nix-snowflake";
exec = "nixos-help";
categories = [ "System" ];
};
in pkgs.symlinkJoin {
in
pkgs.symlinkJoin {
name = "nixos-help";
paths = [
helpScript
@ -187,11 +208,14 @@ in
../config/system-path.nix
../system/etc/etc.nix
(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" ])
(mkRemovedOptionModule
[ "documentation" "nixos" "options" "allowDocBook" ]
"DocBook option documentation is no longer supported")
(mkRemovedOptionModule [
"documentation"
"nixos"
"options"
"allowDocBook"
] "DocBook option documentation is no longer supported")
];
options = {
@ -280,7 +304,7 @@ in
nixos.extraModules = mkOption {
type = types.listOf types.raw;
default = [];
default = [ ];
description = ''
Modules for which to show options even when not imported.
'';
@ -380,9 +404,13 @@ in
(mkIf cfg.nixos.enable {
system.build.manual = manual;
environment.systemPackages = []
environment.systemPackages =
[ ]
++ 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

@ -1,23 +1,28 @@
# Provide a basic configuration for installation devices like CDs.
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
with lib;
{
imports =
[ # Enable devices which are usually scanned, because we don't know the
# target system.
../installer/scan/detected.nix
../installer/scan/not-detected.nix
imports = [
# Enable devices which are usually scanned, because we don't know the
# target system.
../installer/scan/detected.nix
../installer/scan/not-detected.nix
# Allow "nixos-rebuild" to work properly by providing
# /etc/nixos/configuration.nix.
./clone-config.nix
# Allow "nixos-rebuild" to work properly by providing
# /etc/nixos/configuration.nix.
./clone-config.nix
# Include a copy of Nixpkgs so that nixos-install works out of
# the box.
../installer/cd-dvd/channel.nix
];
# Include a copy of Nixpkgs so that nixos-install works out of
# the box.
../installer/cd-dvd/channel.nix
];
config = {
system.nixos.variant_id = lib.mkDefault "installer";
@ -31,7 +36,11 @@ with lib;
# Use less privileged nixos user
users.users.nixos = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" ];
extraGroups = [
"wheel"
"networkmanager"
"video"
];
# Allow the graphical user to login without password
initialHashedPassword = "";
};
@ -52,21 +61,23 @@ with lib;
services.getty.autologinUser = "nixos";
# Some more help text.
services.getty.helpLine = ''
The "nixos" and "root" accounts have empty passwords.
services.getty.helpLine =
''
The "nixos" and "root" accounts have empty passwords.
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
/home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys.
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
/home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys.
If you need a wireless connection, type
`sudo systemctl start wpa_supplicant` and configure a
network using `wpa_cli`. See the NixOS manual for details.
'' + optionalString config.services.xserver.enable ''
If you need a wireless connection, type
`sudo systemctl start wpa_supplicant` and configure a
network using `wpa_cli`. See the NixOS manual for details.
''
+ optionalString config.services.xserver.enable ''
Type `sudo systemctl start display-manager' to
start the graphical user interface.
'';
Type `sudo systemctl start display-manager' to
start the graphical user interface.
'';
# 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.
@ -81,7 +92,7 @@ with lib;
# Enable wpa_supplicant, but don't start it by default.
networking.wireless.enable = mkDefault 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.
# This is desirable in memory-constrained environments that don't
@ -97,7 +108,8 @@ with lib;
# To speed up installation a little bit, include the complete
# stdenv in the Nix store on the CD.
system.extraDependencies = with pkgs;
system.extraDependencies =
with pkgs;
[
stdenv
stdenvNoCC # for runCommand

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
@ -7,13 +12,13 @@ let
cfg = config.programs.fish;
fishAbbrs = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}")
cfg.shellAbbrs
lib.mapAttrsToList (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}") cfg.shellAbbrs
);
fishAliases = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}")
(lib.filterAttrs (k: v: v != null) cfg.shellAliases)
lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}") (
lib.filterAttrs (k: v: v != null) cfg.shellAliases
)
);
envShellInit = pkgs.writeText "shellInit" cfge.shellInit;
@ -22,17 +27,19 @@ let
envInteractiveShellInit = pkgs.writeText "interactiveShellInit" cfge.interactiveShellInit;
sourceEnv = file:
if cfg.useBabelfish then
"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 -e fish_function_path[1]
'';
sourceEnv =
file:
if cfg.useBabelfish then
"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 -e fish_function_path[1]
'';
babelfishTranslate = path: name:
babelfishTranslate =
path: name:
pkgs.runCommand "${name}.fish" {
preferLocalBuild = true;
nativeBuildInputs = [ pkgs.babelfish ];
@ -90,7 +97,7 @@ in
};
shellAbbrs = lib.mkOption {
default = {};
default = { };
example = {
gco = "git checkout";
npu = "nix-prefetch-url";
@ -102,7 +109,7 @@ in
};
shellAliases = lib.mkOption {
default = {};
default = { };
description = ''
Set of aliases for fish shell, which overrides {option}`environment.shellAliases`.
See {option}`environment.shellAliases` for an option format description.
@ -154,16 +161,16 @@ in
documentation.man.generateCaches = lib.mkDefault true;
environment = lib.mkMerge [
(lib.mkIf cfg.useBabelfish
{
etc."fish/setEnvironment.fish".source = babelfishTranslate config.system.build.setEnvironment "setEnvironment";
(lib.mkIf cfg.useBabelfish {
etc."fish/setEnvironment.fish".source =
babelfishTranslate config.system.build.setEnvironment "setEnvironment";
etc."fish/shellInit.fish".source = babelfishTranslate envShellInit "shellInit";
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/loginShellInit".source = envLoginShellInit;
etc."fish/foreign-env/interactiveShellInit".source = envInteractiveShellInit;
@ -171,110 +178,120 @@ in
{
etc."fish/nixos-env-preinit.fish".text =
if cfg.useBabelfish
then ''
# source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
source /etc/fish/setEnvironment.fish
end
''
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
set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions
if cfg.useBabelfish then
''
# source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
source /etc/fish/setEnvironment.fish
end
''
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
set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions
# source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
fenv source ${config.system.build.setEnvironment}
end
# source the NixOS environment config
if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
fenv source ${config.system.build.setEnvironment}
end
# clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
set -e fish_function_path
'';
# clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
set -e fish_function_path
'';
}
{
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 not set -q __fish_nixos_general_config_sourced
${sourceEnv "shellInit"}
# if we haven't sourced the general config, do it
if not set -q __fish_nixos_general_config_sourced
${sourceEnv "shellInit"}
${cfg.shellInit}
${cfg.shellInit}
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_nixos_general_config_sourced 1
end
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_nixos_general_config_sourced 1
end
# if we haven't sourced the login config, do it
status is-login; and not set -q __fish_nixos_login_config_sourced
and begin
${sourceEnv "loginShellInit"}
# if we haven't sourced the login config, do it
status is-login; and not set -q __fish_nixos_login_config_sourced
and begin
${sourceEnv "loginShellInit"}
${cfg.loginShellInit}
${cfg.loginShellInit}
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_nixos_login_config_sourced 1
end
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_nixos_login_config_sourced 1
end
# if we haven't sourced the interactive config, do it
status is-interactive; and not set -q __fish_nixos_interactive_config_sourced
and begin
${fishAbbrs}
${fishAliases}
# if we haven't sourced the interactive config, do it
status is-interactive; and not set -q __fish_nixos_interactive_config_sourced
and begin
${fishAbbrs}
${fishAliases}
${sourceEnv "interactiveShellInit"}
${sourceEnv "interactiveShellInit"}
${cfg.promptInit}
${cfg.interactiveShellInit}
${cfg.promptInit}
${cfg.interactiveShellInit}
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew,
# allowing configuration changes in, e.g, aliases, to propagate)
set -g __fish_nixos_interactive_config_sourced 1
end
'';
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew,
# allowing configuration changes in, e.g, aliases, to propagate)
set -g __fish_nixos_interactive_config_sourced 1
end
'';
}
{
etc."fish/generated_completions".source =
let
patchedGenerator = pkgs.stdenv.mkDerivation {
name = "fish_patched-completion-generator";
srcs = [
"${cfg.package}/share/fish/tools/create_manpage_completions.py"
"${cfg.package}/share/fish/tools/deroff.py"
];
unpackCmd = "cp $curSrc $(basename $curSrc)";
sourceRoot = ".";
patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files
dontBuild = true;
installPhase = ''
mkdir -p $out
cp * $out/
'';
preferLocalBuild = true;
allowSubstitutes = false;
};
generateCompletions = package: pkgs.runCommand
( with lib.strings; let
storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash
pathName = substring storeLength (stringLength package - storeLength) package;
in (package.name or pathName) + "_fish-completions")
( { inherit package;
preferLocalBuild = true;
} //
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
let
patchedGenerator = pkgs.stdenv.mkDerivation {
name = "fish_patched-completion-generator";
srcs = [
"${cfg.package}/share/fish/tools/create_manpage_completions.py"
"${cfg.package}/share/fish/tools/deroff.py"
];
unpackCmd = "cp $curSrc $(basename $curSrc)";
sourceRoot = ".";
patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files
dontBuild = true;
installPhase = ''
mkdir -p $out
cp * $out/
'';
preferLocalBuild = true;
allowSubstitutes = false;
};
generateCompletions =
package:
pkgs.runCommand
(
with lib.strings;
let
storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash
pathName = substring storeLength (stringLength package - storeLength) package;
in
(package.name or pathName) + "_fish-completions"
)
(
{
inherit package;
preferLocalBuild = true;
}
// 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 {
name = "system_fish-completions";
ignoreCollisions = true;
@ -284,10 +301,11 @@ in
# include programs that bring their own completions
{
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.functions.enable "/share/fish/vendor_functions.d";
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.functions.enable "/share/fish/vendor_functions.d";
}
{ systemPackages = [ cfg.package ]; }

View file

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

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.git;
@ -19,27 +24,43 @@ in
let
gitini = attrsOf (attrsOf anything);
in
either gitini (listOf gitini) // {
merge = loc: defs:
either gitini (listOf gitini)
// {
merge =
loc: defs:
let
config = builtins.foldl'
(acc: { value, ... }@x: acc // (if builtins.isList value then {
ordered = acc.ordered ++ value;
} else {
unordered = acc.unordered ++ [ x ];
}))
{
ordered = [ ];
unordered = [ ];
}
defs;
config =
builtins.foldl'
(
acc:
{ value, ... }@x:
acc
// (
if builtins.isList value then
{
ordered = acc.ordered ++ value;
}
else
{
unordered = acc.unordered ++ [ x ];
}
)
)
{
ordered = [ ];
unordered = [ ];
}
defs;
in
[ (gitini.merge loc config.unordered) ] ++ config.ordered;
};
default = [ ];
example = {
init.defaultBranch = "main";
url."https://github.com/".insteadOf = [ "gh:" "github:" ];
url."https://github.com/".insteadOf = [
"gh:"
"github:"
];
};
description = ''
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 ];
@ -23,6 +28,6 @@
config = lib.mkIf config.programs.gphoto2.enable {
services.udev.packages = [ pkgs.libgphoto2 ];
environment.systemPackages = [ pkgs.gphoto2 ];
users.groups.camera = {};
users.groups.camera = { };
};
}

View file

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

View file

@ -2,7 +2,10 @@
{
imports = [
(lib.mkRemovedOptionModule [ "programs" "k3b" "enable" ]
"Please add kdePackages.k3b to environment.systemPackages instead")
(lib.mkRemovedOptionModule [
"programs"
"k3b"
"enable"
] "Please add kdePackages.k3b to environment.systemPackages instead")
];
}

View file

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

View file

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

View file

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

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.steam;
@ -6,9 +11,12 @@ let
extraCompatPaths = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
steam-gamescope = let
exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
in
steam-gamescope =
let
exports = builtins.attrValues (
builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env
);
in
pkgs.writeShellScriptBin "steam-gamescope" ''
${builtins.concatStringsSep "\n" exports}
gamescope --steam ${builtins.toString cfg.gamescopeSession.args} -- steam ${builtins.toString cfg.gamescopeSession.steamArgs}
@ -21,8 +29,12 @@ let
Comment=A digital distribution platform
Exec=${steam-gamescope}/bin/steam-gamescope
Type=Application
'').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; });
in {
'').overrideAttrs
(_: {
passthru.providedSessions = [ "steam" ];
});
in
{
options.programs.steam = {
enable = lib.mkEnableOption "steam";
@ -42,27 +54,40 @@ in {
];
}
'';
apply = steam: steam.override (prev: {
extraEnv = (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths;
}) // (lib.optionalAttrs cfg.extest.enable {
LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
}) // (prev.extraEnv or {});
extraLibraries = pkgs: let
prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
additionalLibs = 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}/..";
};
});
apply =
steam:
steam.override (
prev:
{
extraEnv =
(lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths;
})
// (lib.optionalAttrs cfg.extest.enable {
LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
})
// (prev.extraEnv or { });
extraLibraries =
pkgs:
let
prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
additionalLibs =
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 = ''
The Steam package to use. Additional libraries are added from the system
configuration to ensure graphics work properly.
@ -141,7 +166,7 @@ in {
gamescopeSession = lib.mkOption {
description = "Run a GameScope driven Steam session from your display-manager";
default = {};
default = { };
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "GameScope Session";
@ -187,7 +212,8 @@ in {
};
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;
enable32Bit = true;
};
@ -205,7 +231,9 @@ in {
programs.steam.extraPackages = cfg.fontPackages;
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
services.pulseaudio.support32Bit = config.services.pulseaudio.enable;
@ -213,11 +241,15 @@ in {
hardware.steam-hardware.enable = true;
environment.systemPackages = [
cfg.package
cfg.package.run
] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope
++ lib.optional cfg.protontricks.enable (cfg.protontricks.package.override { inherit extraCompatPaths; });
environment.systemPackages =
[
cfg.package
cfg.package.run
]
++ lib.optional cfg.gamescopeSession.enable steam-gamescope
++ lib.optional cfg.protontricks.enable (
cfg.protontricks.package.override { inherit extraCompatPaths; }
);
networking.firewall = lib.mkMerge [
(lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
@ -226,7 +258,12 @@ in {
(lib.mkIf cfg.remotePlay.openFirewall {
allowedTCPPorts = [ 27036 ];
allowedUDPPortRanges = [ { from = 27031; to = 27035; } ];
allowedUDPPortRanges = [
{
from = 27031;
to = 27035;
}
];
})
(lib.mkIf cfg.dedicatedServer.openFirewall {

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.sway;
@ -14,33 +19,43 @@ in
<https://github.com/swaywm/sway/wiki> and
"man 5 sway" for more information'';
package = lib.mkPackageOption pkgs "sway" {
nullable = true;
extraDescription = ''
If the package is not overridable with `extraSessionCommands`, `extraOptions`,
`withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`,
then the module options {option}`wrapperFeatures`, {option}`extraSessionCommands`,
{option}`extraOptions` and {option}`xwayland` will have no effect.
package =
lib.mkPackageOption pkgs "sway" {
nullable = true;
extraDescription = ''
If the package is not overridable with `extraSessionCommands`, `extraOptions`,
`withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`,
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.
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 {
extraSessionCommands = cfg.extraSessionCommands;
extraOptions = cfg.extraOptions;
withBaseWrapper = cfg.wrapperFeatures.base;
withGtkWrapper = cfg.wrapperFeatures.gtk;
enableXWayland = cfg.xwayland.enable;
isNixOS = true;
};
};
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.
'';
}
// {
apply =
p:
if p == null then
null
else
wayland-lib.genFinalPackage p {
extraSessionCommands = cfg.extraSessionCommands;
extraOptions = cfg.extraOptions;
withBaseWrapper = cfg.wrapperFeatures.base;
withGtkWrapper = cfg.wrapperFeatures.gtk;
enableXWayland = cfg.xwayland.enable;
isNixOS = true;
};
};
wrapperFeatures = {
base = lib.mkEnableOption ''
the base wrapper to execute extra session commands and prepend a
dbus-run-session to the sway command'' // { default = true; };
base =
lib.mkEnableOption ''
the base wrapper to execute extra session commands and prepend a
dbus-run-session to the sway command''
// {
default = true;
};
gtk = lib.mkEnableOption ''
the wrapGAppsHook wrapper to execute sway with required environment
variables for GTK applications'';
@ -69,7 +84,7 @@ in
extraOptions = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
default = [ ];
example = [
"--verbose"
"--debug"
@ -81,12 +96,22 @@ in
'';
};
xwayland.enable = lib.mkEnableOption "XWayland" // { default = true; };
xwayland.enable = lib.mkEnableOption "XWayland" // {
default = true;
};
extraPackages = lib.mkOption {
type = with lib.types; listOf package;
# 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 ''
with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ];
'';
@ -102,18 +127,19 @@ in
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
assertions = [
{
assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
message = ''
The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled.
'';
}
];
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
assertions = [
{
assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
message = ''
The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled.
'';
}
];
warnings =
warnings =
lib.mkIf
(
(lib.elem "nvidia" config.services.xserver.videoDrivers)
@ -123,58 +149,64 @@ in
"Using Sway with Nvidia driver version <= 550 may result in a broken system. Configure hardware.nvidia.package to use a newer version."
];
environment = {
systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
environment = {
systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
# Needed for the default wallpaper:
pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway";
# Needed for the default wallpaper:
pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway";
etc = {
"sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
# Import the most important environment variables into the D-Bus and systemd
# user environments (e.g. required for screen sharing and Pinentry prompts):
exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
# enable systemd-integration
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";
etc =
{
"sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
# Import the most important environment variables into the D-Bus and systemd
# user environments (e.g. required for screen sharing and Pinentry prompts):
exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
# enable systemd-integration
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";
};
};
};
systemd.user.targets.sway-session = {
description = "sway compositor session";
documentation = [ "man:systemd.special(7)" ];
bindsTo = [ "graphical-session.target" ];
wants = [ "graphical-session-pre.target" ];
after = [ "graphical-session-pre.target" ];
};
systemd.user.targets.sway-session = {
description = "sway compositor session";
documentation = [ "man:systemd.special(7)" ];
bindsTo = [ "graphical-session.target" ];
wants = [ "graphical-session-pre.target" ];
after = [ "graphical-session-pre.target" ];
};
# To make a Sway session available if a display manager like SDDM is enabled:
services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;
# To make a Sway session available if a display manager like SDDM is enabled:
services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;
# 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/pull/315
xdg.portal.config.sway = {
# Use xdg-desktop-portal-gtk for every portal interface...
default = [ "gtk" ];
# ... except for the ScreenCast, Screenshot and Secret
"org.freedesktop.impl.portal.ScreenCast" = "wlr";
"org.freedesktop.impl.portal.Screenshot" = "wlr";
# ignore inhibit bc gtk portal always returns as success,
# despite sway/the wlr portal not having an implementation,
# stopping firefox from using wayland idle-inhibit
"org.freedesktop.impl.portal.Inhibit" = "none";
};
}
# 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/pull/315
xdg.portal.config.sway = {
# Use xdg-desktop-portal-gtk for every portal interface...
default = [ "gtk" ];
# ... except for the ScreenCast, Screenshot and Secret
"org.freedesktop.impl.portal.ScreenCast" = "wlr";
"org.freedesktop.impl.portal.Screenshot" = "wlr";
# ignore inhibit bc gtk portal always returns as success,
# despite sway/the wlr portal not having an implementation,
# stopping firefox from using wayland idle-inhibit
"org.freedesktop.impl.portal.Inhibit" = "none";
};
}
(import ./wayland-session.nix {
inherit lib pkgs;
enableXWayland = cfg.xwayland.enable;
})
]);
(import ./wayland-session.nix {
inherit lib pkgs;
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.
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
@ -45,7 +50,9 @@ in
example = lib.literalExpression ''
ps: with ps; [ numpy xonsh.xontribs.xontrib-vox ]
'';
type = with lib.types; coercedTo (listOf lib.types.package) (v: (_: v)) (functionTo (listOf lib.types.package));
type =
with lib.types;
coercedTo (listOf lib.types.package) (v: (_: v)) (functionTo (listOf lib.types.package));
description = ''
Xontribs and extra Python packages to be available in xonsh.
'';

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -4,12 +4,10 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.security.ipa;
pyBool = x:
if x
then "True"
else "False";
pyBool = x: if x then "True" else "False";
ldapConf = pkgs.writeText "ldap.conf" ''
# Turning this off breaks GSSAPI used with krb5 when rdns = false
@ -21,14 +19,16 @@ with lib; let
'';
nssDb =
pkgs.runCommand "ipa-nssdb"
{
nativeBuildInputs = [pkgs.nss.tools];
} ''
mkdir -p $out
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 {
{
nativeBuildInputs = [ pkgs.nss.tools ];
}
''
mkdir -p $out
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
{
options = {
security.ipa = {
enable = mkEnableOption "FreeIPA domain integration";
@ -88,8 +88,11 @@ in {
ipaHostname = mkOption {
type = types.str;
example = "myworkstation.example.com";
default = if config.networking.domain != null then config.networking.fqdn
else "${config.networking.hostName}.${cfg.domain}";
default =
if config.networking.domain != null then
config.networking.fqdn
else
"${config.networking.hostName}.${cfg.domain}";
defaultText = literalExpression ''
if config.networking.domain != null then config.networking.fqdn
else "''${networking.hostName}.''${security.ipa.domain}"
@ -99,7 +102,7 @@ in {
ifpAllowedUids = mkOption {
type = types.listOf types.str;
default = ["root"];
default = [ "root" ];
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 = {
"ipa/default.conf".text = ''
@ -195,7 +201,10 @@ in {
systemd.services."ipa-activation" = {
wantedBy = [ "sysinit.target" ];
before = [ "sysinit.target" "shutdown.target" ];
before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ];
unitConfig.DefaultDependencies = false;
serviceConfig.Type = "oneshot";
@ -234,8 +243,7 @@ in {
cache_credentials = ${pyBool cfg.cacheCredentials}
krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords}
${optionalString ((toLower cfg.domain) != (toLower cfg.realm))
"krb5_realm = ${cfg.realm}"}
${optionalString ((toLower cfg.domain) != (toLower cfg.realm)) "krb5_realm = ${cfg.realm}"}
dyndns_update = ${pyBool cfg.dyndns.enable}
dyndns_iface = ${cfg.dyndns.interface}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,13 @@
# A module for rtkit, a DBus system service that hands out realtime
# scheduling priority to processes that ask for it.
{ config, lib, pkgs, utils, ... }:
{
config,
lib,
pkgs,
utils,
...
}:
with lib;
@ -9,7 +15,8 @@ let
cfg = config.security.rtkit;
package = pkgs.rtkit;
in {
in
{
options = {
@ -26,7 +33,7 @@ in {
security.rtkit.args = mkOption {
type = types.listOf types.str;
default = [];
default = [ ];
description = ''
Command-line options for `rtkit-daemon`.
'';
@ -38,7 +45,6 @@ in {
};
config = mkIf cfg.enable {
security.polkit.enable = true;
@ -52,18 +58,17 @@ in {
systemd.services.rtkit-daemon = {
serviceConfig.ExecStart = [
"" # Resets command from upstream unit.
"" # Resets command from upstream unit.
"${package}/libexec/rtkit-daemon ${utils.escapeSystemdExecArgs cfg.args}"
];
};
users.users.rtkit =
{
isSystemUser = true;
group = "rtkit";
description = "RealtimeKit daemon";
};
users.groups.rtkit = {};
users.users.rtkit = {
isSystemUser = true;
group = "rtkit";
description = "RealtimeKit daemon";
};
users.groups.rtkit = { };
};

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
inherit (config.security) wrapperDir;
@ -11,26 +16,30 @@ let
# 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
# quite small, so linking it statically is more appropriate.
securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix {
inherit sourceProg;
securityWrapper =
sourceProg:
pkgs.pkgsStatic.callPackage ./wrapper.nix {
inherit sourceProg;
# glibc definitions of insecure environment variables
#
# 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.
#
# 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,
# so we maintain a slightly stricter list in wrapper.c itself as well.
unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc)
({ name, ... }: {
name = "${name}-unsecvars";
installPhase = ''
mkdir $out
cp sysdeps/generic/unsecvars.h $out
'';
});
};
# glibc definitions of insecure environment variables
#
# 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.
#
# 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,
# so we maintain a slightly stricter list in wrapper.c itself as well.
unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc) (
{ name, ... }:
{
name = "${name}-unsecvars";
installPhase = ''
mkdir $out
cp sysdeps/generic/unsecvars.h $out
'';
}
);
};
fileModeType =
let
@ -39,45 +48,46 @@ let
numeric = "[-+=]?[0-7]{0,4}";
mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
in
lib.types.strMatching mode
// { description = "file mode string"; };
lib.types.strMatching mode // { description = "file mode string"; };
wrapperType = lib.types.submodule ({ name, config, ... }: {
options.enable = lib.mkOption
{ type = lib.types.bool;
wrapperType = lib.types.submodule (
{ name, config, ... }:
{
options.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable the wrapper.";
};
options.source = lib.mkOption
{ type = lib.types.path;
options.source = lib.mkOption {
type = lib.types.path;
description = "The absolute path to the program to be wrapped.";
};
options.program = lib.mkOption
{ type = with lib.types; nullOr str;
options.program = lib.mkOption {
type = with lib.types; nullOr str;
default = name;
description = ''
The name of the wrapper program. Defaults to the attribute name.
'';
};
options.owner = lib.mkOption
{ type = lib.types.str;
options.owner = lib.mkOption {
type = lib.types.str;
description = "The owner of the wrapper program.";
};
options.group = lib.mkOption
{ type = lib.types.str;
options.group = lib.mkOption {
type = lib.types.str;
description = "The group of the wrapper program.";
};
options.permissions = lib.mkOption
{ type = fileModeType;
default = "u+rx,g+x,o+x";
options.permissions = lib.mkOption {
type = fileModeType;
default = "u+rx,g+x,o+x";
example = "a+rx";
description = ''
The permissions of the wrapper program. The format is that of a
symbolic or numeric file mode understood by {command}`chmod`.
'';
};
options.capabilities = lib.mkOption
{ type = lib.types.commas;
options.capabilities = lib.mkOption {
type = lib.types.commas;
default = "";
description = ''
A comma-separated list of capability clauses to be given to the
@ -96,27 +106,29 @@ let
:::
'';
};
options.setuid = lib.mkOption
{ type = lib.types.bool;
options.setuid = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to add the setuid bit the wrapper program.";
};
options.setgid = lib.mkOption
{ type = lib.types.bool;
options.setgid = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to add the setgid bit the wrapper program.";
};
});
}
);
###### Activation script for the setcap wrappers
mkSetcapProgram =
{ program
, capabilities
, source
, owner
, group
, permissions
, ...
{
program,
capabilities,
source,
owner,
group,
permissions,
...
}:
''
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
@ -136,14 +148,15 @@ let
###### Activation script for the setuid wrappers
mkSetuidProgram =
{ program
, source
, owner
, group
, setuid
, setgid
, permissions
, ...
{
program,
source,
owner,
group,
setuid,
setgid,
permissions,
...
}:
''
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
@ -155,13 +168,9 @@ let
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
'';
mkWrappedPrograms =
builtins.map
(opts:
if opts.capabilities != ""
then mkSetcapProgram opts
else mkSetuidProgram opts
) (lib.attrValues wrappers);
mkWrappedPrograms = builtins.map (
opts: if opts.capabilities != "" then mkSetcapProgram opts else mkSetuidProgram opts
) (lib.attrValues wrappers);
in
{
imports = [
@ -178,35 +187,34 @@ in
security.wrappers = lib.mkOption {
type = lib.types.attrsOf wrapperType;
default = {};
example = lib.literalExpression
''
{
# a setuid root program
doas =
{ setuid = true;
owner = "root";
group = "root";
source = "''${pkgs.doas}/bin/doas";
};
default = { };
example = lib.literalExpression ''
{
# a setuid root program
doas =
{ setuid = true;
owner = "root";
group = "root";
source = "''${pkgs.doas}/bin/doas";
};
# a setgid program
locate =
{ setgid = true;
owner = "root";
group = "mlocate";
source = "''${pkgs.locate}/bin/locate";
};
# a setgid program
locate =
{ setgid = true;
owner = "root";
group = "mlocate";
source = "''${pkgs.locate}/bin/locate";
};
# a program with the CAP_NET_RAW capability
ping =
{ owner = "root";
group = "root";
capabilities = "cap_net_raw+ep";
source = "''${pkgs.iputils.out}/bin/ping";
};
}
'';
# a program with the CAP_NET_RAW capability
ping =
{ owner = "root";
group = "root";
capabilities = "cap_net_raw+ep";
source = "''${pkgs.iputils.out}/bin/ping";
};
}
'';
description = ''
This option effectively allows adding setuid/setgid bits, capabilities,
changing file ownership and permissions of a program without directly
@ -226,9 +234,9 @@ in
};
security.wrapperDir = lib.mkOption {
type = lib.types.path;
default = "/run/wrappers/bin";
internal = true;
type = lib.types.path;
default = "/run/wrappers/bin";
internal = true;
description = ''
This option defines the path to the wrapper programs. It
should not be overridden.
@ -239,29 +247,28 @@ in
###### implementation
config = lib.mkIf config.security.enableWrappers {
assertions = lib.mapAttrsToList
(name: opts:
{ assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
message = ''
The security.wrappers.${name} wrapper is not valid:
setuid/setgid and capabilities are mutually exclusive.
'';
}
) wrappers;
assertions = lib.mapAttrsToList (name: opts: {
assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
message = ''
The security.wrappers.${name} wrapper is not valid:
setuid/setgid and capabilities are mutually exclusive.
'';
}) wrappers;
security.wrappers =
let
mkSetuidRoot = source:
{ setuid = true;
owner = "root";
group = "root";
inherit source;
};
mkSetuidRoot = source: {
setuid = true;
owner = "root";
group = "root";
inherit source;
};
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";
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";
};
@ -272,33 +279,45 @@ in
export PATH="${wrapperDir}:$PATH"
'';
security.apparmor.includes = lib.mapAttrs' (wrapName: wrap: lib.nameValuePair
"nixos/security.wrappers/${wrapName}" ''
include "${pkgs.apparmorRulesFromClosure { name="security.wrappers.${wrapName}"; } [
(securityWrapper wrap.source)
]}"
mrpx ${wrap.source},
'') wrappers;
security.apparmor.includes = lib.mapAttrs' (
wrapName: wrap:
lib.nameValuePair "nixos/security.wrappers/${wrapName}" ''
include "${
pkgs.apparmorRulesFromClosure { name = "security.wrappers.${wrapName}"; } [
(securityWrapper wrap.source)
]
}"
mrpx ${wrap.source},
''
) wrappers;
systemd.mounts = [{
where = parentWrapperDir;
what = "tmpfs";
type = "tmpfs";
options = lib.concatStringsSep "," ([
"nodev"
"mode=755"
"size=${config.security.wrapperDirSize}"
]);
}];
systemd.mounts = [
{
where = parentWrapperDir;
what = "tmpfs";
type = "tmpfs";
options = lib.concatStringsSep "," ([
"nodev"
"mode=755"
"size=${config.security.wrapperDirSize}"
]);
}
];
systemd.services.suid-sgid-wrappers = {
description = "Create SUID/SGID Wrappers";
wantedBy = [ "sysinit.target" ];
before = [ "sysinit.target" "shutdown.target" ];
before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ];
after = [ "systemd-sysusers.service" ];
unitConfig.DefaultDependencies = false;
unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ];
unitConfig.RequiresMountsFor = [
"/nix/store"
"/run/wrappers"
];
serviceConfig.Type = "oneshot";
script = ''
chmod 755 "${parentWrapperDir}"
@ -327,31 +346,34 @@ in
};
###### wrappers consistency checks
system.checks = lib.singleton (pkgs.runCommand "ensure-all-wrappers-paths-exist" {
preferLocalBuild = true;
} ''
# make sure we produce output
mkdir -p $out
system.checks = lib.singleton (
pkgs.runCommand "ensure-all-wrappers-paths-exist"
{
preferLocalBuild = true;
}
''
# make sure we produce output
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
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v:
"wrappers['${n}']='${v.source}'") wrappers)}
declare -A wrappers
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "wrappers['${n}']='${v.source}'") wrappers)}
for name in "''${!wrappers[@]}"; do
path="''${wrappers[$name]}"
if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then
test -t 1 && echo -ne '\033[1;31m'
echo "FAIL"
echo "The path $path does not exist!"
echo 'Please, check the value of `security.wrappers."'$name'".source`.'
test -t 1 && echo -ne '\033[0m'
exit 1
fi
done
for name in "''${!wrappers[@]}"; do
path="''${wrappers[$name]}"
if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then
test -t 1 && echo -ne '\033[1;31m'
echo "FAIL"
echo "The path $path does not exist!"
echo 'Please, check the value of `security.wrappers."'$name'".source`.'
test -t 1 && echo -ne '\033[0m'
exit 1
fi
done
echo "OK"
'');
echo "OK"
''
);
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.gonic;
settingsFormat = pkgs.formats.keyValue {
@ -42,7 +47,9 @@ in
ExecStart =
let
# these values are null by default but should not appear in the final config
filteredSettings = lib.filterAttrs (n: v: !((n == "tls-cert" || n == "tls-key") && v == null)) cfg.settings;
filteredSettings = lib.filterAttrs (
n: v: !((n == "tls-cert" || n == "tls-key") && v == null)
) cfg.settings;
in
"${pkgs.gonic}/bin/gonic -config-path ${settingsFormat.generate "gonic" filteredSettings}";
DynamicUser = true;
@ -56,16 +63,22 @@ in
cfg.settings.playlists-path
cfg.settings.podcast-path
];
BindReadOnlyPaths = [
# gonic can access scrobbling services
"-/etc/resolv.conf"
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
builtins.storeDir
] ++ 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;
BindReadOnlyPaths =
[
# gonic can access scrobbling services
"-/etc/resolv.conf"
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
builtins.storeDir
]
++ 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 = "";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
PrivateDevices = true;
PrivateUsers = true;
@ -76,7 +89,10 @@ in
ProtectKernelModules = true;
ProtectKernelTunables = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
SystemCallFilter = [
"@system-service"
"~@privileged"
];
RestrictRealtime = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
name = "mpd";
@ -7,13 +12,17 @@ let
gid = config.ids.gids.mpd;
cfg = config.services.mpd;
credentialsPlaceholder = (creds:
credentialsPlaceholder = (
creds:
let
placeholders = (lib.imap0
(i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"'')
creds);
placeholders = (
lib.imap0 (
i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"''
) creds
);
in
lib.concatStringsSep "\n" placeholders);
lib.concatStringsSep "\n" placeholders
);
mpdConf = pkgs.writeText "mpd.conf" ''
# This file was automatically generated by NixOS. Edit mpd's configuration
@ -28,8 +37,10 @@ let
state_file "${cfg.dataDir}/state"
sticker_file "${cfg.dataDir}/sticker.sql"
${lib.optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
${lib.optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
${lib.optionalString (
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) ''
decoder {
plugin "fluidsynth"
@ -37,12 +48,13 @@ let
}
''}
${lib.optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)}
${lib.optionalString (cfg.credentials != [ ]) (credentialsPlaceholder cfg.credentials)}
${cfg.extraConfig}
'';
in {
in
{
###### interface
@ -160,33 +172,53 @@ in {
};
credentials = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
passwordFile = lib.mkOption {
type = lib.types.path;
description = ''
Path to file containing the password.
'';
type = lib.types.listOf (
lib.types.submodule {
options = {
passwordFile = lib.mkOption {
type = lib.types.path;
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 = ''
Credentials and permissions for accessing the mpd server.
'';
default = [];
default = [ ];
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
config = lib.mkIf cfg.enable {
@ -212,10 +243,15 @@ in {
systemd.sockets.mpd = lib.mkIf cfg.startWhenNeeded {
wantedBy = [ "sockets.target" ];
listenStreams = [
"" # Note: this is needed to override the upstream unit
(if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
then cfg.network.listenAddress
else "${lib.optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}")
"" # Note: this is needed to override the upstream unit
(
if pkgs.lib.hasPrefix "/" cfg.network.listenAddress then
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
install -m 600 ${mpdConf} /run/mpd/mpd.conf
'' + lib.optionalString (cfg.credentials != [])
(lib.concatStringsSep "\n"
(lib.imap0
(i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'')
cfg.credentials));
''
+ lib.optionalString (cfg.credentials != [ ]) (
lib.concatStringsSep "\n" (
lib.imap0 (
i: c:
''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf''
) cfg.credentials
)
);
serviceConfig =
{
User = "${cfg.user}";
# Note: the first "" overrides the ExecStart from the upstream unit
ExecStart = [ "" "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" ];
RuntimeDirectory = "mpd";
StateDirectory = []
++ 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" ];
};
serviceConfig = {
User = "${cfg.user}";
# Note: the first "" overrides the ExecStart from the upstream unit
ExecStart = [
""
"${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf"
];
RuntimeDirectory = "mpd";
StateDirectory =
[ ]
++ 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) {

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,54 +1,69 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.syncoid;
# Extract local dasaset names (so no datasets containing "@")
localDatasetName = d: lib.optionals (d != null) (
let m = builtins.match "([^/@]+[^@]*)" d; in
lib.optionals (m != null) m
);
localDatasetName =
d:
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
escapeUnitName = name:
lib.concatMapStrings (s: if lib.isList s then "-" else s)
(builtins.split "[^a-zA-Z0-9_.\\-]+" name);
escapeUnitName =
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
# permissions to. It also checks if the target dataset exists before
# 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
# datasets.
buildAllowCommand = permissions: dataset: (
"-+${pkgs.writeShellScript "zfs-allow-${dataset}" ''
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
buildAllowCommand =
permissions: dataset:
(
"-+${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
if ${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"list"
dataset
]} 2> /dev/null; then
${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"allow"
cfg.user
(lib.concatStringsSep "," permissions)
dataset
]}
${lib.optionalString ((builtins.dirOf dataset) != ".") ''
else
# Run a ZFS list on the dataset to check if it exists
if ${
lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"list"
dataset
]
} 2> /dev/null; then
${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"allow"
cfg.user
(lib.concatStringsSep "," permissions)
# Remove the last part of the path
(builtins.dirOf dataset)
dataset
]}
''}
fi
''}"
);
${lib.optionalString ((builtins.dirOf dataset) != ".") ''
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
# 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
# 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.
buildUnallowCommand = permissions: dataset: (
"-+${pkgs.writeShellScript "zfs-unallow-${dataset}" ''
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"unallow"
cfg.user
(lib.concatStringsSep "," permissions)
dataset
]}
${lib.optionalString ((builtins.dirOf dataset) != ".") (lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"unallow"
cfg.user
(lib.concatStringsSep "," permissions)
# Remove the last part of the path
(builtins.dirOf dataset)
])}
''}"
);
buildUnallowCommand =
permissions: dataset:
(
"-+${pkgs.writeShellScript "zfs-unallow-${dataset}" ''
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
${lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"unallow"
cfg.user
(lib.concatStringsSep "," permissions)
dataset
]}
${lib.optionalString ((builtins.dirOf dataset) != ".") (
lib.escapeShellArgs [
"/run/booted-system/sw/bin/zfs"
"unallow"
cfg.user
(lib.concatStringsSep "," permissions)
# Remove the last part of the path
(builtins.dirOf dataset)
]
)}
''}"
);
in
{
@ -84,7 +103,7 @@ in
options.services.syncoid = {
enable = lib.mkEnableOption "Syncoid ZFS synchronization service";
package = lib.mkPackageOption pkgs "sanoid" {};
package = lib.mkPackageOption pkgs "sanoid" { };
interval = lib.mkOption {
type = lib.types.str;
@ -131,7 +150,14 @@ in
localSourceAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
# 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 = ''
Permissions granted for the {option}`services.syncoid.user` user
for local source datasets. See
@ -142,8 +168,21 @@ in
localTargetAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "change-key" "compression" "create" "mount" "mountpoint" "receive" "rollback" ];
example = [ "create" "mount" "receive" "rollback" ];
default = [
"change-key"
"compression"
"create"
"mount"
"mountpoint"
"receive"
"rollback"
];
example = [
"create"
"mount"
"receive"
"rollback"
];
description = ''
Permissions granted for the {option}`services.syncoid.user` user
for local target datasets. See
@ -176,111 +215,116 @@ in
};
commands = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
options = {
source = lib.mkOption {
type = lib.types.str;
example = "pool/dataset";
description = ''
Source ZFS dataset. Can be either local or remote. Defaults to
the attribute name.
'';
};
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
source = lib.mkOption {
type = lib.types.str;
example = "pool/dataset";
description = ''
Source ZFS dataset. Can be either local or remote. Defaults to
the attribute name.
'';
};
target = lib.mkOption {
type = lib.types.str;
example = "user@server:pool/dataset";
description = ''
Target ZFS dataset. Can be either local
(«pool/dataset») or remote
(«user@server:pool/dataset»).
'';
};
target = lib.mkOption {
type = lib.types.str;
example = "user@server:pool/dataset";
description = ''
Target ZFS dataset. Can be either local
(«pool/dataset») or remote
(«user@server:pool/dataset»).
'';
};
recursive = lib.mkEnableOption ''the transfer of child datasets'';
recursive = lib.mkEnableOption ''the transfer of child datasets'';
sshKey = lib.mkOption {
type = with lib.types; nullOr (coercedTo path toString str);
description = ''
SSH private key file to use to login to the remote system.
Defaults to {option}`services.syncoid.sshKey` option.
'';
};
sshKey = lib.mkOption {
type = with lib.types; nullOr (coercedTo path toString str);
description = ''
SSH private key file to use to login to the remote system.
Defaults to {option}`services.syncoid.sshKey` option.
'';
};
localSourceAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Permissions granted for the {option}`services.syncoid.user` user
for local source datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions.
Defaults to {option}`services.syncoid.localSourceAllow` option.
'';
};
localSourceAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Permissions granted for the {option}`services.syncoid.user` user
for local source datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions.
Defaults to {option}`services.syncoid.localSourceAllow` option.
'';
};
localTargetAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Permissions granted for the {option}`services.syncoid.user` user
for local target datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions.
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.
For remote target datasets you'll have to set your remote user permissions by yourself.
'';
};
localTargetAllow = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Permissions granted for the {option}`services.syncoid.user` user
for local target datasets. See
<https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
for available permissions.
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.
For remote target datasets you'll have to set your remote user permissions by yourself.
'';
};
sendOptions = lib.mkOption {
type = lib.types.separatedString " ";
default = "";
example = "Lc e";
description = ''
Advanced options to pass to zfs send. Options are specified
without their leading dashes and separated by spaces.
'';
};
sendOptions = lib.mkOption {
type = lib.types.separatedString " ";
default = "";
example = "Lc e";
description = ''
Advanced options to pass to zfs send. Options are specified
without their leading dashes and separated by spaces.
'';
};
recvOptions = lib.mkOption {
type = lib.types.separatedString " ";
default = "";
example = "ux recordsize o compression=lz4";
description = ''
Advanced options to pass to zfs recv. Options are specified
without their leading dashes and separated by spaces.
'';
};
recvOptions = lib.mkOption {
type = lib.types.separatedString " ";
default = "";
example = "ux recordsize o compression=lz4";
description = ''
Advanced options to pass to zfs recv. Options are specified
without their leading dashes and separated by spaces.
'';
};
useCommonArgs = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to add the configured common arguments to this command.
'';
};
useCommonArgs = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to add the configured common arguments to this command.
'';
};
service = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
Systemd configuration specific to this syncoid service.
'';
};
service = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
Systemd configuration specific to this syncoid service.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "--sshport 2222" ];
description = "Extra syncoid arguments for this command.";
};
};
config = {
source = lib.mkDefault name;
sshKey = lib.mkDefault cfg.sshKey;
localSourceAllow = lib.mkDefault cfg.localSourceAllow;
localTargetAllow = lib.mkDefault cfg.localTargetAllow;
};
}));
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "--sshport 2222" ];
description = "Extra syncoid arguments for this command.";
};
};
config = {
source = lib.mkDefault name;
sshKey = lib.mkDefault cfg.sshKey;
localSourceAllow = lib.mkDefault cfg.localSourceAllow;
localTargetAllow = lib.mkDefault cfg.localTargetAllow;
};
}
)
);
default = { };
example = lib.literalExpression ''
{
@ -310,9 +354,10 @@ in
};
};
systemd.services = lib.mapAttrs'
(name: c:
lib.nameValuePair "syncoid-${escapeUnitName name}" (lib.mkMerge [
systemd.services = lib.mapAttrs' (
name: c:
lib.nameValuePair "syncoid-${escapeUnitName name}" (
lib.mkMerge [
{
description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}";
after = [ "zfs.target" ];
@ -321,25 +366,30 @@ in
path = [ "/run/booted-system/sw/bin/" ];
serviceConfig = {
ExecStartPre =
(map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source)) ++
(map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target));
(map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source))
++ (map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target));
ExecStopPost =
(map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source)) ++
(map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
ExecStart = lib.escapeShellArgs ([ "${cfg.package}/bin/syncoid" ]
(map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source))
++ (map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
ExecStart = lib.escapeShellArgs (
[ "${cfg.package}/bin/syncoid" ]
++ lib.optionals c.useCommonArgs cfg.commonArgs
++ lib.optional c.recursive "-r"
++ lib.optionals (c.sshKey != null) [ "--sshkey" c.sshKey ]
++ lib.optionals (c.sshKey != null) [
"--sshkey"
c.sshKey
]
++ c.extraArgs
++ [
"--sendoptions"
c.sendOptions
"--recvoptions"
c.recvOptions
"--no-privilege-elevation"
c.source
c.target
]);
"--sendoptions"
c.sendOptions
"--recvoptions"
c.recvOptions
"--no-privilege-elevation"
c.source
c.target
]
);
User = cfg.user;
Group = cfg.group;
StateDirectory = [ "syncoid" ];
@ -372,14 +422,23 @@ in
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RootDirectory = "/run/syncoid/${escapeUnitName name}";
RootDirectoryStartOnly = true;
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.
InaccessiblePaths = [ "-+/run/syncoid/${escapeUnitName name}" ];
MountAPIVFS = true;
@ -409,9 +468,13 @@ in
}
cfg.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
version = "1.10.1";
cfg = config.services.kubernetes.addons.dns;
@ -7,7 +13,8 @@ let
health = 10054;
metrics = 10055;
};
in {
in
{
options.services.kubernetes.addons.dns = {
enable = lib.mkEnableOption "kubernetes dns addon";
@ -15,11 +22,11 @@ in {
description = "Dns addon clusterIP";
# this default is also what kubernetes users
default = (
lib.concatStringsSep "." (
lib.take 3 (lib.splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange
default =
(lib.concatStringsSep "." (
lib.take 3 (lib.splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange)
))
) + ".254";
+ ".254";
defaultText = lib.literalMD ''
The `x.y.z.254` IP of
`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>.
'';
default = "Reconcile";
type = lib.types.enum [ "Reconcile" "EnsureExists" ];
type = lib.types.enum [
"Reconcile"
"EnsureExists"
];
};
coredns = lib.mkOption {
@ -106,8 +116,9 @@ in {
};
config = lib.mkIf cfg.enable {
services.kubernetes.kubelet.seedDockerImages =
lib.singleton (pkgs.dockerTools.pullImage cfg.coredns);
services.kubernetes.kubelet.seedDockerImages = lib.singleton (
pkgs.dockerTools.pullImage cfg.coredns
);
services.kubernetes.addonManager.bootstrapAddons = {
coredns-cr = {
@ -125,8 +136,16 @@ in {
rules = [
{
apiGroups = [ "" ];
resources = [ "endpoints" "services" "pods" "namespaces" ];
verbs = [ "list" "watch" ];
resources = [
"endpoints"
"services"
"pods"
"namespaces"
];
verbs = [
"list"
"watch"
];
}
{
apiGroups = [ "" ];
@ -136,7 +155,10 @@ in {
{
apiGroups = [ "discovery.k8s.io" ];
resources = [ "endpointslices" ];
verbs = [ "list" "watch" ];
verbs = [
"list"
"watch"
];
}
];
};
@ -219,10 +241,14 @@ in {
spec = {
replicas = cfg.replicas;
selector = {
matchLabels = { k8s-app = "kube-dns"; };
matchLabels = {
k8s-app = "kube-dns";
};
};
strategy = {
rollingUpdate = { maxUnavailable = 1; };
rollingUpdate = {
maxUnavailable = 1;
};
type = "RollingUpdate";
};
template = {
@ -234,7 +260,10 @@ in {
spec = {
containers = [
{
args = [ "-conf" "/etc/coredns/Corefile" ];
args = [
"-conf"
"/etc/coredns/Corefile"
];
image = with cfg.coredns; "${imageName}:${finalImageTag}";
imagePullPolicy = "Never";
livenessProbe = {
@ -358,7 +387,9 @@ in {
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,
...
}:
let
top = config.services.kubernetes;
otop = options.services.kubernetes;
@ -6,22 +12,40 @@ let
isRBACEnabled = lib.elem "RBAC" cfg.authorizationMode;
apiserverServiceIP = (lib.concatStringsSep "." (
lib.take 3 (lib.splitString "." cfg.serviceClusterIpRange
)) + ".1");
apiserverServiceIP = (
lib.concatStringsSep "." (lib.take 3 (lib.splitString "." cfg.serviceClusterIpRange)) + ".1"
);
in
{
imports = [
(lib.mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ])
(lib.mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "apiserver" "admissionControl" ]
[ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ]
)
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "apiserver" "address" ]
[ "services" "kubernetes" "apiserver" "bindAddress" ]
)
(lib.mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecureBindAddress" ] "")
(lib.mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecurePort" ] "")
(lib.mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
(lib.mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ])
(lib.mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ])
(lib.mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ])
(lib.mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ])
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "servers" ]
[ "services" "kubernetes" "apiserver" "etcd" "servers" ]
)
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "keyFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "keyFile" ]
)
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "certFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "certFile" ]
)
(lib.mkRenamedOptionModule
[ "services" "kubernetes" "etcd" "caFile" ]
[ "services" "kubernetes" "apiserver" "etcd" "caFile" ]
)
];
###### interface
@ -48,8 +72,18 @@ in
Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See
<https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
'';
default = ["RBAC" "Node"]; # Enabling RBAC by default, although kubernetes default is AllowAllow
type = listOf (enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "Webhook" "RBAC" "Node"]);
default = [
"RBAC"
"Node"
]; # Enabling RBAC by default, although kubernetes default is AllowAllow
type = listOf (enum [
"AlwaysAllow"
"AlwaysDeny"
"ABAC"
"Webhook"
"RBAC"
"Node"
]);
};
authorizationPolicy = lib.mkOption {
@ -57,7 +91,7 @@ in
Kubernetes apiserver authorization policy file. See
<https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
'';
default = [];
default = [ ];
type = listOf attrs;
};
@ -92,7 +126,7 @@ in
Kubernetes admission control plugins to disable. See
<https://kubernetes.io/docs/admin/admission-controllers/>
'';
default = [];
default = [ ];
type = listOf str;
};
@ -104,14 +138,24 @@ in
<https://kubernetes.io/docs/admin/admission-controllers/>
'';
default = [
"NamespaceLifecycle" "LimitRanger" "ServiceAccount"
"ResourceQuota" "DefaultStorageClass" "DefaultTolerationSeconds"
"NamespaceLifecycle"
"LimitRanger"
"ServiceAccount"
"ResourceQuota"
"DefaultStorageClass"
"DefaultTolerationSeconds"
"NodeRestriction"
];
example = [
"NamespaceLifecycle" "NamespaceExists" "LimitRanger"
"SecurityContextDeny" "ServiceAccount" "ResourceQuota"
"PodSecurityPolicy" "NodeRestriction" "DefaultStorageClass"
"NamespaceLifecycle"
"NamespaceExists"
"LimitRanger"
"SecurityContextDeny"
"ServiceAccount"
"ResourceQuota"
"PodSecurityPolicy"
"NodeRestriction"
"DefaultStorageClass"
];
type = listOf str;
};
@ -119,7 +163,7 @@ in
etcd = {
servers = lib.mkOption {
description = "List of etcd servers.";
default = ["http://127.0.0.1:2379"];
default = [ "http://127.0.0.1:2379" ];
type = types.listOf types.str;
};
@ -151,7 +195,7 @@ in
extraSANs = lib.mkOption {
description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
default = [];
default = [ ];
type = listOf str;
};
@ -214,7 +258,10 @@ in
Kubernetes apiserver storage backend.
'';
default = "etcd3";
type = enum ["etcd2" "etcd3"];
type = enum [
"etcd2"
"etcd3"
];
};
securePort = lib.mkOption {
@ -309,135 +356,143 @@ in
};
###### implementation
config = lib.mkMerge [
(lib.mkIf cfg.enable {
systemd.services.kube-apiserver = {
description = "Kubernetes APIServer Service";
wantedBy = [ "kubernetes.target" ];
after = [ "network.target" ];
serviceConfig = {
Slice = "kubernetes.slice";
ExecStart = ''
${top.package}/bin/kube-apiserver \
--allow-privileged=${lib.boolToString cfg.allowPrivileged} \
--authorization-mode=${lib.concatStringsSep "," cfg.authorizationMode} \
${lib.optionalString (lib.elem "ABAC" cfg.authorizationMode)
"--authorization-policy-file=${
pkgs.writeText "kube-auth-policy.jsonl"
(lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy)
}"
} \
${lib.optionalString (lib.elem "Webhook" cfg.authorizationMode)
"--authorization-webhook-config-file=${cfg.webhookConfig}"
} \
--bind-address=${cfg.bindAddress} \
${lib.optionalString (cfg.advertiseAddress != null)
"--advertise-address=${cfg.advertiseAddress}"} \
${lib.optionalString (cfg.clientCaFile != null)
"--client-ca-file=${cfg.clientCaFile}"} \
--disable-admission-plugins=${lib.concatStringsSep "," cfg.disableAdmissionPlugins} \
--enable-admission-plugins=${lib.concatStringsSep "," cfg.enableAdmissionPlugins} \
--etcd-servers=${lib.concatStringsSep "," cfg.etcd.servers} \
${lib.optionalString (cfg.etcd.caFile != null)
"--etcd-cafile=${cfg.etcd.caFile}"} \
${lib.optionalString (cfg.etcd.certFile != null)
"--etcd-certfile=${cfg.etcd.certFile}"} \
${lib.optionalString (cfg.etcd.keyFile != null)
"--etcd-keyfile=${cfg.etcd.keyFile}"} \
${lib.optionalString (cfg.featureGates != {})
"--feature-gates=${(lib.concatStringsSep "," (builtins.attrValues (lib.mapAttrs (n: v: "${n}=${lib.trivial.boolToString v}") cfg.featureGates)))}"} \
${lib.optionalString (cfg.basicAuthFile != null)
"--basic-auth-file=${cfg.basicAuthFile}"} \
${lib.optionalString (cfg.kubeletClientCaFile != null)
"--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"} \
${lib.optionalString (cfg.kubeletClientCertFile != null)
"--kubelet-client-certificate=${cfg.kubeletClientCertFile}"} \
${lib.optionalString (cfg.kubeletClientKeyFile != null)
"--kubelet-client-key=${cfg.kubeletClientKeyFile}"} \
${lib.optionalString (cfg.preferredAddressTypes != null)
"--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"} \
${lib.optionalString (cfg.proxyClientCertFile != null)
"--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \
${lib.optionalString (cfg.proxyClientKeyFile != null)
"--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \
${lib.optionalString (cfg.runtimeConfig != "")
"--runtime-config=${cfg.runtimeConfig}"} \
--secure-port=${toString cfg.securePort} \
--api-audiences=${toString cfg.apiAudiences} \
--service-account-issuer=${toString cfg.serviceAccountIssuer} \
--service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \
--service-account-key-file=${cfg.serviceAccountKeyFile} \
--service-cluster-ip-range=${cfg.serviceClusterIpRange} \
--storage-backend=${cfg.storageBackend} \
${lib.optionalString (cfg.tlsCertFile != null)
"--tls-cert-file=${cfg.tlsCertFile}"} \
${lib.optionalString (cfg.tlsKeyFile != null)
"--tls-private-key-file=${cfg.tlsKeyFile}"} \
${lib.optionalString (cfg.tokenAuthFile != null)
"--token-auth-file=${cfg.tokenAuthFile}"} \
${lib.optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
${cfg.extraOpts}
'';
WorkingDirectory = top.dataDir;
User = "kubernetes";
Group = "kubernetes";
AmbientCapabilities = "cap_net_bind_service";
Restart = "on-failure";
RestartSec = 5;
};
unitConfig = {
StartLimitIntervalSec = 0;
};
systemd.services.kube-apiserver = {
description = "Kubernetes APIServer Service";
wantedBy = [ "kubernetes.target" ];
after = [ "network.target" ];
serviceConfig = {
Slice = "kubernetes.slice";
ExecStart = ''
${top.package}/bin/kube-apiserver \
--allow-privileged=${lib.boolToString cfg.allowPrivileged} \
--authorization-mode=${lib.concatStringsSep "," cfg.authorizationMode} \
${lib.optionalString (lib.elem "ABAC" cfg.authorizationMode) "--authorization-policy-file=${pkgs.writeText "kube-auth-policy.jsonl" (lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy)}"} \
${lib.optionalString (lib.elem "Webhook" cfg.authorizationMode) "--authorization-webhook-config-file=${cfg.webhookConfig}"} \
--bind-address=${cfg.bindAddress} \
${lib.optionalString (cfg.advertiseAddress != null) "--advertise-address=${cfg.advertiseAddress}"} \
${lib.optionalString (cfg.clientCaFile != null) "--client-ca-file=${cfg.clientCaFile}"} \
--disable-admission-plugins=${lib.concatStringsSep "," cfg.disableAdmissionPlugins} \
--enable-admission-plugins=${lib.concatStringsSep "," cfg.enableAdmissionPlugins} \
--etcd-servers=${lib.concatStringsSep "," cfg.etcd.servers} \
${lib.optionalString (cfg.etcd.caFile != null) "--etcd-cafile=${cfg.etcd.caFile}"} \
${lib.optionalString (cfg.etcd.certFile != null) "--etcd-certfile=${cfg.etcd.certFile}"} \
${lib.optionalString (cfg.etcd.keyFile != null) "--etcd-keyfile=${cfg.etcd.keyFile}"} \
${
lib.optionalString (cfg.featureGates != { })
"--feature-gates=${
(lib.concatStringsSep "," (
builtins.attrValues (lib.mapAttrs (n: v: "${n}=${lib.trivial.boolToString v}") cfg.featureGates)
))
}"
} \
${lib.optionalString (cfg.basicAuthFile != null) "--basic-auth-file=${cfg.basicAuthFile}"} \
${
lib.optionalString (
cfg.kubeletClientCaFile != null
) "--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"
} \
${
lib.optionalString (
cfg.kubeletClientCertFile != null
) "--kubelet-client-certificate=${cfg.kubeletClientCertFile}"
} \
${
lib.optionalString (
cfg.kubeletClientKeyFile != null
) "--kubelet-client-key=${cfg.kubeletClientKeyFile}"
} \
${
lib.optionalString (
cfg.preferredAddressTypes != null
) "--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"
} \
${
lib.optionalString (
cfg.proxyClientCertFile != null
) "--proxy-client-cert-file=${cfg.proxyClientCertFile}"
} \
${
lib.optionalString (
cfg.proxyClientKeyFile != null
) "--proxy-client-key-file=${cfg.proxyClientKeyFile}"
} \
${lib.optionalString (cfg.runtimeConfig != "") "--runtime-config=${cfg.runtimeConfig}"} \
--secure-port=${toString cfg.securePort} \
--api-audiences=${toString cfg.apiAudiences} \
--service-account-issuer=${toString cfg.serviceAccountIssuer} \
--service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \
--service-account-key-file=${cfg.serviceAccountKeyFile} \
--service-cluster-ip-range=${cfg.serviceClusterIpRange} \
--storage-backend=${cfg.storageBackend} \
${lib.optionalString (cfg.tlsCertFile != null) "--tls-cert-file=${cfg.tlsCertFile}"} \
${lib.optionalString (cfg.tlsKeyFile != null) "--tls-private-key-file=${cfg.tlsKeyFile}"} \
${lib.optionalString (cfg.tokenAuthFile != null) "--token-auth-file=${cfg.tokenAuthFile}"} \
${lib.optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
${cfg.extraOpts}
'';
WorkingDirectory = top.dataDir;
User = "kubernetes";
Group = "kubernetes";
AmbientCapabilities = "cap_net_bind_service";
Restart = "on-failure";
RestartSec = 5;
};
services.etcd = {
clientCertAuth = lib.mkDefault true;
peerClientCertAuth = lib.mkDefault true;
listenClientUrls = lib.mkDefault ["https://0.0.0.0:2379"];
listenPeerUrls = lib.mkDefault ["https://0.0.0.0:2380"];
advertiseClientUrls = lib.mkDefault ["https://${top.masterAddress}:2379"];
initialCluster = lib.mkDefault ["${top.masterAddress}=https://${top.masterAddress}:2380"];
name = lib.mkDefault top.masterAddress;
initialAdvertisePeerUrls = lib.mkDefault ["https://${top.masterAddress}:2380"];
unitConfig = {
StartLimitIntervalSec = 0;
};
};
services.kubernetes.addonManager.bootstrapAddons = lib.mkIf isRBACEnabled {
services.etcd = {
clientCertAuth = lib.mkDefault true;
peerClientCertAuth = lib.mkDefault true;
listenClientUrls = lib.mkDefault [ "https://0.0.0.0:2379" ];
listenPeerUrls = lib.mkDefault [ "https://0.0.0.0:2380" ];
advertiseClientUrls = lib.mkDefault [ "https://${top.masterAddress}:2379" ];
initialCluster = lib.mkDefault [ "${top.masterAddress}=https://${top.masterAddress}:2380" ];
name = lib.mkDefault top.masterAddress;
initialAdvertisePeerUrls = lib.mkDefault [ "https://${top.masterAddress}:2380" ];
};
apiserver-kubelet-api-admin-crb = {
apiVersion = "rbac.authorization.k8s.io/v1";
kind = "ClusterRoleBinding";
metadata = {
name = "system:kube-apiserver:kubelet-api-admin";
};
roleRef = {
apiGroup = "rbac.authorization.k8s.io";
kind = "ClusterRole";
name = "system:kubelet-api-admin";
};
subjects = [{
services.kubernetes.addonManager.bootstrapAddons = lib.mkIf isRBACEnabled {
apiserver-kubelet-api-admin-crb = {
apiVersion = "rbac.authorization.k8s.io/v1";
kind = "ClusterRoleBinding";
metadata = {
name = "system:kube-apiserver:kubelet-api-admin";
};
roleRef = {
apiGroup = "rbac.authorization.k8s.io";
kind = "ClusterRole";
name = "system:kubelet-api-admin";
};
subjects = [
{
kind = "User";
name = "system:kube-apiserver";
}];
};
}
];
};
};
services.kubernetes.pki.certs = with top.lib; {
apiServer = mkCert {
name = "kube-apiserver";
CN = "kubernetes";
hosts = [
"kubernetes.default.svc"
"kubernetes.default.svc.${top.addons.dns.clusterDomain}"
cfg.advertiseAddress
top.masterAddress
apiserverServiceIP
"127.0.0.1"
] ++ cfg.extraSANs;
"kubernetes.default.svc"
"kubernetes.default.svc.${top.addons.dns.clusterDomain}"
cfg.advertiseAddress
top.masterAddress
apiserverServiceIP
"127.0.0.1"
] ++ cfg.extraSANs;
action = "systemctl restart kube-apiserver.service";
};
apiserverProxyClient = mkCert {
@ -467,11 +522,11 @@ in
name = "etcd";
CN = top.masterAddress;
hosts = [
"etcd.local"
"etcd.${top.addons.dns.clusterDomain}"
top.masterAddress
cfg.advertiseAddress
];
"etcd.local"
"etcd.${top.addons.dns.clusterDomain}"
top.masterAddress
cfg.advertiseAddress
];
privateKeyOwner = "etcd";
action = "systemctl restart etcd.service";
};

View file

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

View file

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

View file

@ -1,142 +1,166 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.buildkite-agents;
hooksDir = hooks:
hooksDir =
hooks:
let
mkHookEntry = name: text: ''
ln --symbolic ${pkgs.writeShellApplication { inherit name text; }}/bin/${name} $out/${name}
'';
in
pkgs.runCommand "buildkite-agent-hooks" {
preferLocalBuild = true;
} ''
mkdir $out
${lib.concatStringsSep "\n" (lib.mapAttrsToList mkHookEntry hooks)}
'';
pkgs.runCommand "buildkite-agent-hooks"
{
preferLocalBuild = true;
}
''
mkdir $out
${lib.concatStringsSep "\n" (lib.mapAttrsToList mkHookEntry hooks)}
'';
buildkiteOptions = { name ? "", config, ... }: {
options = {
enable = lib.mkOption {
default = true;
type = lib.types.bool;
description = "Whether to enable this buildkite agent";
};
buildkiteOptions =
{
name ? "",
config,
...
}:
{
options = {
enable = lib.mkOption {
default = true;
type = lib.types.bool;
description = "Whether to enable this buildkite agent";
};
package = lib.mkOption {
default = pkgs.buildkite-agent;
defaultText = lib.literalExpression "pkgs.buildkite-agent";
description = "Which buildkite-agent derivation to use";
type = lib.types.package;
};
package = lib.mkOption {
default = pkgs.buildkite-agent;
defaultText = lib.literalExpression "pkgs.buildkite-agent";
description = "Which buildkite-agent derivation to use";
type = lib.types.package;
};
dataDir = lib.mkOption {
default = "/var/lib/buildkite-agent-${name}";
description = "The workdir for the agent";
type = lib.types.str;
};
dataDir = lib.mkOption {
default = "/var/lib/buildkite-agent-${name}";
description = "The workdir for the agent";
type = lib.types.str;
};
extraGroups = lib.mkOption {
default = [ "keys" ];
description = "Groups the user for this buildkite agent should belong to";
type = lib.types.listOf lib.types.str;
};
extraGroups = lib.mkOption {
default = [ "keys" ];
description = "Groups the user for this buildkite agent should belong to";
type = lib.types.listOf lib.types.str;
};
runtimePackages = lib.mkOption {
default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
description = "Add programs to the buildkite-agent environment";
type = lib.types.listOf lib.types.package;
};
runtimePackages = lib.mkOption {
default = [
pkgs.bash
pkgs.gnutar
pkgs.gzip
pkgs.git
pkgs.nix
];
defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
description = "Add programs to the buildkite-agent environment";
type = lib.types.listOf lib.types.package;
};
tokenPath = lib.mkOption {
type = lib.types.path;
description = ''
The token from your Buildkite "Agents" page.
tokenPath = lib.mkOption {
type = lib.types.path;
description = ''
The token from your Buildkite "Agents" page.
A run-time path to the token file, which is supposed to be provisioned
outside of Nix store.
'';
};
A run-time path to the token file, which is supposed to be provisioned
outside of Nix store.
'';
};
name = lib.mkOption {
type = lib.types.str;
default = "%hostname-${name}-%n";
description = ''
The name of the agent as seen in the buildkite dashboard.
'';
};
name = lib.mkOption {
type = lib.types.str;
default = "%hostname-${name}-%n";
description = ''
The name of the agent as seen in the buildkite dashboard.
'';
};
tags = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = { };
example = { queue = "default"; docker = "true"; ruby2 = "true"; };
description = ''
Tags for the agent.
'';
};
tags = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = { };
example = {
queue = "default";
docker = "true";
ruby2 = "true";
};
description = ''
Tags for the agent.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = "debug=true";
description = ''
Extra lines to be added verbatim to the configuration file.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = "debug=true";
description = ''
Extra lines to be added verbatim to the configuration file.
'';
};
privateSshKeyPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
## maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
apply = final: if final == null then null else toString final;
privateSshKeyPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
## maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
apply = final: if final == null then null else toString final;
description = ''
OpenSSH private key
description = ''
OpenSSH private key
A run-time path to the key file, which is supposed to be provisioned
outside of Nix store.
'';
};
A run-time path to the key file, which is supposed to be provisioned
outside of Nix store.
'';
};
hooks = lib.mkOption {
type = lib.types.attrsOf lib.types.lines;
default = { };
example = lib.literalExpression ''
{
environment = '''
export SECRET_VAR=`head -1 /run/keys/secret`
''';
}'';
description = ''
"Agent" hooks to install.
See <https://buildkite.com/docs/agent/v3/hooks> for possible options.
'';
};
hooks = lib.mkOption {
type = lib.types.attrsOf lib.types.lines;
default = { };
example = lib.literalExpression ''
{
environment = '''
export SECRET_VAR=`head -1 /run/keys/secret`
''';
}'';
description = ''
"Agent" hooks to install.
See <https://buildkite.com/docs/agent/v3/hooks> for possible options.
'';
};
hooksPath = lib.mkOption {
type = lib.types.path;
default = hooksDir config.hooks;
defaultText = lib.literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
description = ''
Path to the directory storing the hooks.
Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
instead.
'';
};
hooksPath = lib.mkOption {
type = lib.types.path;
default = hooksDir config.hooks;
defaultText = lib.literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
description = ''
Path to the directory storing the hooks.
Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
instead.
'';
};
shell = lib.mkOption {
type = lib.types.str;
default = "${pkgs.bash}/bin/bash -e -c";
defaultText = lib.literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
description = ''
Command that buildkite-agent 3 will execute when it spawns a shell.
'';
shell = lib.mkOption {
type = lib.types.str;
default = "${pkgs.bash}/bin/bash -e -c";
defaultText = lib.literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
description = ''
Command that buildkite-agent 3 will execute when it spawns a shell.
'';
};
};
};
};
enabledAgents = lib.filterAttrs (n: v: v.enable) cfg;
mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents);
in
@ -152,76 +176,92 @@ in
'';
};
config.users.users = mapAgents (name: cfg: {
"buildkite-agent-${name}" = {
name = "buildkite-agent-${name}";
home = cfg.dataDir;
createHome = true;
description = "Buildkite agent user";
extraGroups = cfg.extraGroups;
isSystemUser = true;
group = "buildkite-agent-${name}";
};
});
config.users.groups = mapAgents (name: cfg: {
"buildkite-agent-${name}" = { };
});
config.systemd.services = mapAgents (name: cfg: {
"buildkite-agent-${name}" = {
description = "Buildkite Agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ];
environment = config.networking.proxy.envVars // {
HOME = cfg.dataDir;
NIX_REMOTE = "daemon";
config.users.users = mapAgents (
name: cfg: {
"buildkite-agent-${name}" = {
name = "buildkite-agent-${name}";
home = cfg.dataDir;
createHome = true;
description = "Buildkite agent user";
extraGroups = cfg.extraGroups;
isSystemUser = true;
group = "buildkite-agent-${name}";
};
}
);
config.users.groups = mapAgents (
name: cfg: {
"buildkite-agent-${name}" = { };
}
);
## NB: maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
preStart =
let
sshDir = "${cfg.dataDir}/.ssh";
tagStr = name: value:
if lib.isList value
then lib.concatStringsSep "," (builtins.map (v: "${name}=${v}") value)
else "${name}=${value}";
tagsStr = lib.concatStringsSep "," (lib.mapAttrsToList tagStr cfg.tags);
in
lib.optionalString (cfg.privateSshKeyPath != null) ''
mkdir -m 0700 -p "${sshDir}"
install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
'' + ''
cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
token="$(cat ${toString cfg.tokenPath})"
name="${cfg.name}"
shell="${cfg.shell}"
tags="${tagsStr}"
build-path="${cfg.dataDir}/builds"
hooks-path="${cfg.hooksPath}"
${cfg.extraConfig}
EOF
config.systemd.services = mapAgents (
name: cfg: {
"buildkite-agent-${name}" = {
description = "Buildkite Agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = cfg.runtimePackages ++ [
cfg.package
pkgs.coreutils
];
environment = config.networking.proxy.envVars // {
HOME = cfg.dataDir;
NIX_REMOTE = "daemon";
};
## NB: maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
preStart =
let
sshDir = "${cfg.dataDir}/.ssh";
tagStr =
name: value:
if lib.isList value then
lib.concatStringsSep "," (builtins.map (v: "${name}=${v}") value)
else
"${name}=${value}";
tagsStr = lib.concatStringsSep "," (lib.mapAttrsToList tagStr cfg.tags);
in
lib.optionalString (cfg.privateSshKeyPath != null) ''
mkdir -m 0700 -p "${sshDir}"
install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
''
+ ''
cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
token="$(cat ${toString cfg.tokenPath})"
name="${cfg.name}"
shell="${cfg.shell}"
tags="${tagsStr}"
build-path="${cfg.dataDir}/builds"
hooks-path="${cfg.hooksPath}"
${cfg.extraConfig}
EOF
'';
serviceConfig = {
ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
User = "buildkite-agent-${name}";
RestartSec = 5;
Restart = "on-failure";
TimeoutSec = 10;
# set a long timeout to give buildkite-agent a chance to finish current builds
TimeoutStopSec = "2 min";
KillMode = "mixed";
};
};
}
);
config.assertions = mapAgents (
name: cfg: [
{
assertion = cfg.hooksPath != hooksDir cfg.hooks -> cfg.hooks == { };
message = ''
Options `services.buildkite-agents.${name}.hooksPath' and
`services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
'';
serviceConfig = {
ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
User = "buildkite-agent-${name}";
RestartSec = 5;
Restart = "on-failure";
TimeoutSec = 10;
# set a long timeout to give buildkite-agent a chance to finish current builds
TimeoutStopSec = "2 min";
KillMode = "mixed";
};
};
});
config.assertions = mapAgents (name: cfg: [{
assertion = cfg.hooksPath != hooksDir cfg.hooks -> cfg.hooks == { };
message = ''
Options `services.buildkite-agents.${name}.hooksPath' and
`services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
'';
}]);
}
]
);
}

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.hydra;
@ -7,61 +12,71 @@ let
hydraConf = pkgs.writeScript "hydra.conf" cfg.extraConfig;
hydraEnv =
{ HYDRA_DBI = cfg.dbi;
HYDRA_CONFIG = "${baseDir}/hydra.conf";
HYDRA_DATA = "${baseDir}";
};
hydraEnv = {
HYDRA_DBI = cfg.dbi;
HYDRA_CONFIG = "${baseDir}/hydra.conf";
HYDRA_DATA = "${baseDir}";
};
env =
{ NIX_REMOTE = "daemon";
{
NIX_REMOTE = "daemon";
PGPASSFILE = "${baseDir}/pgpass";
NIX_REMOTE_SYSTEMS = lib.concatStringsSep ":" cfg.buildMachinesFiles;
} // lib.optionalAttrs (cfg.smtpHost != null) {
}
// lib.optionalAttrs (cfg.smtpHost != null) {
EMAIL_SENDER_TRANSPORT = "SMTP";
EMAIL_SENDER_TRANSPORT_host = cfg.smtpHost;
} // hydraEnv // cfg.extraEnv;
}
// hydraEnv
// cfg.extraEnv;
serverEnv = env //
{ HYDRA_TRACKER = cfg.tracker;
serverEnv =
env
// {
HYDRA_TRACKER = cfg.tracker;
XDG_CACHE_HOME = "${baseDir}/www/.cache";
COLUMNS = "80";
PGPASSFILE = "${baseDir}/pgpass-www"; # grrr
} // (lib.optionalAttrs cfg.debugServer { DBIC_TRACE = "1"; });
}
// (lib.optionalAttrs cfg.debugServer { DBIC_TRACE = "1"; });
localDB = "dbi:Pg:dbname=hydra;user=hydra;";
haveLocalDB = cfg.dbi == localDB;
hydra-package =
let
makeWrapperArgs = lib.concatStringsSep " " (lib.mapAttrsToList (key: value: "--set-default \"${key}\" \"${value}\"") hydraEnv);
in pkgs.buildEnv rec {
name = "hydra-env";
nativeBuildInputs = [ pkgs.makeWrapper ];
paths = [ cfg.package ];
let
makeWrapperArgs = lib.concatStringsSep " " (
lib.mapAttrsToList (key: value: "--set-default \"${key}\" \"${value}\"") hydraEnv
);
in
pkgs.buildEnv rec {
name = "hydra-env";
nativeBuildInputs = [ pkgs.makeWrapper ];
paths = [ cfg.package ];
postBuild = ''
if [ -L "$out/bin" ]; then
unlink "$out/bin"
fi
mkdir -p "$out/bin"
for path in ${lib.concatStringsSep " " paths}; do
if [ -d "$path/bin" ]; then
cd "$path/bin"
for prg in *; do
if [ -f "$prg" ]; then
rm -f "$out/bin/$prg"
if [ -x "$prg" ]; then
makeWrapper "$path/bin/$prg" "$out/bin/$prg" ${makeWrapperArgs}
fi
fi
done
postBuild = ''
if [ -L "$out/bin" ]; then
unlink "$out/bin"
fi
done
'';
};
mkdir -p "$out/bin"
for path in ${lib.concatStringsSep " " paths}; do
if [ -d "$path/bin" ]; then
cd "$path/bin"
for prg in *; do
if [ -f "$prg" ]; then
rm -f "$out/bin/$prg"
if [ -x "$prg" ]; then
makeWrapper "$path/bin/$prg" "$out/bin/$prg" ${makeWrapperArgs}
fi
fi
done
fi
done
'';
};
in
@ -199,7 +214,7 @@ in
extraEnv = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {};
default = { };
description = "Extra environment variables for Hydra.";
};
@ -211,9 +226,12 @@ in
buildMachinesFiles = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = lib.optional (config.nix.buildMachines != []) "/etc/nix/machines";
default = lib.optional (config.nix.buildMachines != [ ]) "/etc/nix/machines";
defaultText = lib.literalExpression ''lib.optional (config.nix.buildMachines != []) "/etc/nix/machines"'';
example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
example = [
"/etc/nix/machines"
"/var/lib/hydra/provisioner/machines"
];
description = "List of files containing build machines.";
};
@ -234,7 +252,6 @@ in
};
###### implementation
config = lib.mkIf cfg.enable {
@ -253,42 +270,41 @@ in
gid = config.ids.gids.hydra;
};
users.users.hydra =
{ description = "Hydra";
group = "hydra";
# We don't enable `createHome` here because the creation of the home directory is handled by the hydra-init service below.
home = baseDir;
useDefaultShell = true;
uid = config.ids.uids.hydra;
};
users.users.hydra = {
description = "Hydra";
group = "hydra";
# We don't enable `createHome` here because the creation of the home directory is handled by the hydra-init service below.
home = baseDir;
useDefaultShell = true;
uid = config.ids.uids.hydra;
};
users.users.hydra-queue-runner =
{ description = "Hydra queue runner";
group = "hydra";
useDefaultShell = true;
home = "${baseDir}/queue-runner"; # really only to keep SSH happy
uid = config.ids.uids.hydra-queue-runner;
};
users.users.hydra-queue-runner = {
description = "Hydra queue runner";
group = "hydra";
useDefaultShell = true;
home = "${baseDir}/queue-runner"; # really only to keep SSH happy
uid = config.ids.uids.hydra-queue-runner;
};
users.users.hydra-www =
{ description = "Hydra web server";
group = "hydra";
useDefaultShell = true;
uid = config.ids.uids.hydra-www;
};
users.users.hydra-www = {
description = "Hydra web server";
group = "hydra";
useDefaultShell = true;
uid = config.ids.uids.hydra-www;
};
services.hydra.extraConfig =
''
using_frontend_proxy = 1
base_uri = ${cfg.hydraURL}
notification_sender = ${cfg.notificationSender}
max_servers = ${toString cfg.maxServers}
${lib.optionalString (cfg.logo != null) ''
hydra_logo = ${cfg.logo}
''}
gc_roots_dir = ${cfg.gcRootsDir}
use-substitutes = ${if cfg.useSubstitutes then "1" else "0"}
'';
services.hydra.extraConfig = ''
using_frontend_proxy = 1
base_uri = ${cfg.hydraURL}
notification_sender = ${cfg.notificationSender}
max_servers = ${toString cfg.maxServers}
${lib.optionalString (cfg.logo != null) ''
hydra_logo = ${cfg.logo}
''}
gc_roots_dir = ${cfg.gcRootsDir}
use-substitutes = ${if cfg.useSubstitutes then "1" else "0"}
'';
environment.systemPackages = [ hydra-package ];
@ -301,247 +317,264 @@ in
trusted-users = [ "hydra-queue-runner" ];
}
(lib.mkIf (lib.versionOlder (lib.getVersion config.nix.package.out) "2.4pre")
{
# The default (`true') slows Nix down a lot since the build farm
# has so many GC roots.
gc-check-reachability = false;
}
)
(lib.mkIf (lib.versionOlder (lib.getVersion config.nix.package.out) "2.4pre") {
# The default (`true') slows Nix down a lot since the build farm
# has so many GC roots.
gc-check-reachability = false;
})
];
systemd.slices.system-hydra = {
description = "Hydra CI Server Slice";
documentation = [ "file://${cfg.package}/share/doc/hydra/index.html" "https://nixos.org/hydra/manual/" ];
documentation = [
"file://${cfg.package}/share/doc/hydra/index.html"
"https://nixos.org/hydra/manual/"
];
};
systemd.services.hydra-init =
{ wantedBy = [ "multi-user.target" ];
requires = lib.optional haveLocalDB "postgresql.service";
after = lib.optional haveLocalDB "postgresql.service";
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-init";
};
path = [ pkgs.util-linux ];
preStart = ''
mkdir -p ${baseDir}
chown hydra:hydra ${baseDir}
chmod 0750 ${baseDir}
systemd.services.hydra-init = {
wantedBy = [ "multi-user.target" ];
requires = lib.optional haveLocalDB "postgresql.service";
after = lib.optional haveLocalDB "postgresql.service";
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-init";
};
path = [ pkgs.util-linux ];
preStart = ''
mkdir -p ${baseDir}
chown hydra:hydra ${baseDir}
chmod 0750 ${baseDir}
ln -sf ${hydraConf} ${baseDir}/hydra.conf
ln -sf ${hydraConf} ${baseDir}/hydra.conf
mkdir -m 0700 ${baseDir}/www || true
chown hydra-www:hydra ${baseDir}/www
mkdir -m 0700 ${baseDir}/www || true
chown hydra-www:hydra ${baseDir}/www
mkdir -m 0700 ${baseDir}/queue-runner || true
mkdir -m 0750 ${baseDir}/build-logs || true
mkdir -m 0750 ${baseDir}/runcommand-logs || true
chown hydra-queue-runner:hydra \
${baseDir}/queue-runner \
${baseDir}/build-logs \
${baseDir}/runcommand-logs
mkdir -m 0700 ${baseDir}/queue-runner || true
mkdir -m 0750 ${baseDir}/build-logs || true
mkdir -m 0750 ${baseDir}/runcommand-logs || true
chown hydra-queue-runner:hydra \
${baseDir}/queue-runner \
${baseDir}/build-logs \
${baseDir}/runcommand-logs
${lib.optionalString haveLocalDB ''
if ! [ -e ${baseDir}/.db-created ]; then
runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -- -O hydra hydra
touch ${baseDir}/.db-created
fi
echo "create extension if not exists pg_trgm" | runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
''}
${lib.optionalString haveLocalDB ''
if ! [ -e ${baseDir}/.db-created ]; then
runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -- -O hydra hydra
touch ${baseDir}/.db-created
fi
echo "create extension if not exists pg_trgm" | runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
''}
if [ ! -e ${cfg.gcRootsDir} ]; then
if [ ! -e ${cfg.gcRootsDir} ]; then
# Move legacy roots directory.
if [ -e /nix/var/nix/gcroots/per-user/hydra/hydra-roots ]; then
mv /nix/var/nix/gcroots/per-user/hydra/hydra-roots ${cfg.gcRootsDir}
fi
mkdir -p ${cfg.gcRootsDir}
# Move legacy roots directory.
if [ -e /nix/var/nix/gcroots/per-user/hydra/hydra-roots ]; then
mv /nix/var/nix/gcroots/per-user/hydra/hydra-roots ${cfg.gcRootsDir}
fi
# Move legacy hydra-www roots.
if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then
find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f -print0 \
| xargs -0 -r mv -f -t ${cfg.gcRootsDir}/
rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
fi
mkdir -p ${cfg.gcRootsDir}
fi
chown hydra:hydra ${cfg.gcRootsDir}
chmod 2775 ${cfg.gcRootsDir}
'';
serviceConfig.ExecStart = "${hydra-package}/bin/hydra-init";
serviceConfig.PermissionsStartOnly = true;
serviceConfig.User = "hydra";
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.Slice = "system-hydra.slice";
# Move legacy hydra-www roots.
if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then
find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f -print0 \
| xargs -0 -r mv -f -t ${cfg.gcRootsDir}/
rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
fi
chown hydra:hydra ${cfg.gcRootsDir}
chmod 2775 ${cfg.gcRootsDir}
'';
serviceConfig.ExecStart = "${hydra-package}/bin/hydra-init";
serviceConfig.PermissionsStartOnly = true;
serviceConfig.User = "hydra";
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.Slice = "system-hydra.slice";
};
systemd.services.hydra-server = {
wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = serverEnv // {
HYDRA_DBI = "${serverEnv.HYDRA_DBI};application_name=hydra-server";
};
systemd.services.hydra-server =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = serverEnv // {
HYDRA_DBI = "${serverEnv.HYDRA_DBI};application_name=hydra-server";
};
restartTriggers = [ hydraConf ];
serviceConfig =
{ ExecStart =
"@${hydra-package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
+ "-p ${toString cfg.port} --min_spare_servers ${toString cfg.minSpareServers} --max_spare_servers ${toString cfg.maxSpareServers} "
+ "--max_servers ${toString cfg.maxServers} --max_requests 100 ${lib.optionalString cfg.debugServer "-d"}";
User = "hydra-www";
PermissionsStartOnly = true;
Restart = "always";
Slice = "system-hydra.slice";
};
restartTriggers = [ hydraConf ];
serviceConfig = {
ExecStart =
"@${hydra-package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
+ "-p ${toString cfg.port} --min_spare_servers ${toString cfg.minSpareServers} --max_spare_servers ${toString cfg.maxSpareServers} "
+ "--max_servers ${toString cfg.maxServers} --max_requests 100 ${lib.optionalString cfg.debugServer "-d"}";
User = "hydra-www";
PermissionsStartOnly = true;
Restart = "always";
Slice = "system-hydra.slice";
};
};
systemd.services.hydra-queue-runner =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" "network.target" ];
path = [ hydra-package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ];
restartTriggers = [ hydraConf ];
environment = env // {
PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
IN_SYSTEMD = "1"; # to get log severity levels
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-queue-runner";
};
serviceConfig =
{ ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v";
ExecStopPost = "${hydra-package}/bin/hydra-queue-runner --unlock";
User = "hydra-queue-runner";
Restart = "always";
Slice = "system-hydra.slice";
# Ensure we can get core dumps.
LimitCORE = "infinity";
WorkingDirectory = "${baseDir}/queue-runner";
};
systemd.services.hydra-queue-runner = {
wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [
"hydra-init.service"
"network.target"
];
path = [
hydra-package
pkgs.nettools
pkgs.openssh
pkgs.bzip2
config.nix.package
];
restartTriggers = [ hydraConf ];
environment = env // {
PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
IN_SYSTEMD = "1"; # to get log severity levels
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-queue-runner";
};
serviceConfig = {
ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v";
ExecStopPost = "${hydra-package}/bin/hydra-queue-runner --unlock";
User = "hydra-queue-runner";
Restart = "always";
Slice = "system-hydra.slice";
systemd.services.hydra-evaluator =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
wants = [ "network-online.target" ];
after = [ "hydra-init.service" "network.target" "network-online.target" ];
path = with pkgs; [ hydra-package nettools jq ];
restartTriggers = [ hydraConf ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-evaluator";
};
serviceConfig =
{ ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator";
User = "hydra";
Restart = "always";
WorkingDirectory = baseDir;
Slice = "system-hydra.slice";
};
# Ensure we can get core dumps.
LimitCORE = "infinity";
WorkingDirectory = "${baseDir}/queue-runner";
};
};
systemd.services.hydra-update-gc-roots =
{ requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-update-gc-roots";
};
serviceConfig =
{ ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
User = "hydra";
Slice = "system-hydra.slice";
};
startAt = "2,14:15";
systemd.services.hydra-evaluator = {
wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
wants = [ "network-online.target" ];
after = [
"hydra-init.service"
"network.target"
"network-online.target"
];
path = with pkgs; [
hydra-package
nettools
jq
];
restartTriggers = [ hydraConf ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-evaluator";
};
serviceConfig = {
ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator";
User = "hydra";
Restart = "always";
WorkingDirectory = baseDir;
Slice = "system-hydra.slice";
};
};
systemd.services.hydra-send-stats =
{ wantedBy = [ "multi-user.target" ];
after = [ "hydra-init.service" ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-send-stats";
};
serviceConfig =
{ ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats";
User = "hydra";
Slice = "system-hydra.slice";
};
systemd.services.hydra-update-gc-roots = {
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-update-gc-roots";
};
serviceConfig = {
ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
User = "hydra";
Slice = "system-hydra.slice";
};
startAt = "2,14:15";
};
systemd.services.hydra-notify =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
restartTriggers = [ hydraConf ];
path = [ pkgs.zstd ];
environment = env // {
PGPASSFILE = "${baseDir}/pgpass-queue-runner";
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-notify";
};
serviceConfig =
{ ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify";
# FIXME: run this under a less privileged user?
User = "hydra-queue-runner";
Restart = "always";
RestartSec = 5;
Slice = "system-hydra.slice";
};
systemd.services.hydra-send-stats = {
wantedBy = [ "multi-user.target" ];
after = [ "hydra-init.service" ];
environment = env // {
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-send-stats";
};
serviceConfig = {
ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats";
User = "hydra";
Slice = "system-hydra.slice";
};
};
systemd.services.hydra-notify = {
wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
restartTriggers = [ hydraConf ];
path = [ pkgs.zstd ];
environment = env // {
PGPASSFILE = "${baseDir}/pgpass-queue-runner";
HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-notify";
};
serviceConfig = {
ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify";
# FIXME: run this under a less privileged user?
User = "hydra-queue-runner";
Restart = "always";
RestartSec = 5;
Slice = "system-hydra.slice";
};
};
# If there is less than a certain amount of free disk space, stop
# the queue/evaluator to prevent builds from failing or aborting.
systemd.services.hydra-check-space =
{ script =
''
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then
echo "stopping Hydra queue runner due to lack of free space..."
systemctl stop hydra-queue-runner
fi
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFreeEvaluator} * 1024**3)) ]; then
echo "stopping Hydra evaluator due to lack of free space..."
systemctl stop hydra-evaluator
fi
'';
startAt = "*:0/5";
serviceConfig.Slice = "system-hydra.slice";
};
systemd.services.hydra-check-space = {
script = ''
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then
echo "stopping Hydra queue runner due to lack of free space..."
systemctl stop hydra-queue-runner
fi
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFreeEvaluator} * 1024**3)) ]; then
echo "stopping Hydra evaluator due to lack of free space..."
systemctl stop hydra-evaluator
fi
'';
startAt = "*:0/5";
serviceConfig.Slice = "system-hydra.slice";
};
# Periodically compress build logs. The queue runner compresses
# logs automatically after a step finishes, but this doesn't work
# if the queue runner is stopped prematurely.
systemd.services.hydra-compress-logs =
{ path = [ pkgs.bzip2 pkgs.zstd ];
script =
''
set -eou pipefail
compression=$(sed -nr 's/compress_build_logs_compression = ()/\1/p' ${baseDir}/hydra.conf)
if [[ $compression == "" || $compression == bzip2 ]]; then
compressionCmd=(bzip2)
elif [[ $compression == zstd ]]; then
compressionCmd=(zstd --rm)
fi
find ${baseDir}/build-logs -ignore_readdir_race -type f -name "*.drv" -mtime +3 -size +0c -print0 | xargs -0 -r "''${compressionCmd[@]}" --force --quiet
'';
startAt = "Sun 01:45";
serviceConfig.Slice = "system-hydra.slice";
};
systemd.services.hydra-compress-logs = {
path = [
pkgs.bzip2
pkgs.zstd
];
script = ''
set -eou pipefail
compression=$(sed -nr 's/compress_build_logs_compression = ()/\1/p' ${baseDir}/hydra.conf)
if [[ $compression == "" || $compression == bzip2 ]]; then
compressionCmd=(bzip2)
elif [[ $compression == zstd ]]; then
compressionCmd=(zstd --rm)
fi
find ${baseDir}/build-logs -ignore_readdir_race -type f -name "*.drv" -mtime +3 -size +0c -print0 | xargs -0 -r "''${compressionCmd[@]}" --force --quiet
'';
startAt = "Sun 01:45";
serviceConfig.Slice = "system-hydra.slice";
};
services.postgresql.enable = lib.mkIf haveLocalDB true;
services.postgresql.identMap = lib.optionalString haveLocalDB
''
hydra-users hydra hydra
hydra-users hydra-queue-runner hydra
hydra-users hydra-www hydra
hydra-users root hydra
# The postgres user is used to create the pg_trgm extension for the hydra database
hydra-users postgres postgres
'';
services.postgresql.identMap = lib.optionalString haveLocalDB ''
hydra-users hydra hydra
hydra-users hydra-queue-runner hydra
hydra-users hydra-www hydra
hydra-users root hydra
# The postgres user is used to create the pg_trgm extension for the hydra database
hydra-users postgres postgres
'';
services.postgresql.authentication = lib.optionalString haveLocalDB
''
local hydra all ident map=hydra-users
'';
services.postgresql.authentication = lib.optionalString haveLocalDB ''
local hydra all ident map=hydra-users
'';
};

View file

@ -37,10 +37,13 @@ let
# 2. the module configuration
# 3. the extraConfigFiles from the module options
# 4. the locally writable config file, which couchdb itself writes to
configFiles = [
"${cfg.package}/etc/default.ini"
optionsConfigFile
] ++ cfg.extraConfigFiles ++ [ cfg.configFile ];
configFiles =
[
"${cfg.package}/etc/default.ini"
optionsConfigFile
]
++ cfg.extraConfigFiles
++ [ cfg.configFile ];
executable = "${cfg.package}/bin/couchdb";
in
{

View file

@ -1,10 +1,18 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.ferretdb;
in
{
meta.maintainers = with lib.maintainers; [ julienmalka camillemndn ];
meta.maintainers = with lib.maintainers; [
julienmalka
camillemndn
];
options = {
services.ferretdb = {
@ -22,8 +30,11 @@ in
type = lib.types.submodule {
freeformType = with lib.types; attrsOf str;
options = {
FERRETDB_HANDLER = lib.mkOption {
type = lib.types.enum [ "sqlite" "pg" ];
FERRETDB_HANDLER = lib.mkOption {
type = lib.types.enum [
"sqlite"
"pg"
];
default = "sqlite";
description = "Backend handler";
};
@ -41,7 +52,10 @@ in
};
FERRETDB_TELEMETRY = lib.mkOption {
type = lib.types.enum [ "enable" "disable" ];
type = lib.types.enum [
"enable"
"disable"
];
default = "disable";
description = ''
Enable or disable basic telemetry.
@ -64,38 +78,37 @@ in
};
};
config = lib.mkIf cfg.enable
{
services.ferretdb.settings = { };
config = lib.mkIf cfg.enable {
services.ferretdb.settings = { };
systemd.services.ferretdb = {
description = "FerretDB";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = cfg.settings;
serviceConfig = {
Type = "simple";
StateDirectory = "ferretdb";
WorkingDirectory = "/var/lib/ferretdb";
ExecStart = "${cfg.package}/bin/ferretdb";
Restart = "on-failure";
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
PrivateDevices = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
DynamicUser = true;
};
systemd.services.ferretdb = {
description = "FerretDB";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = cfg.settings;
serviceConfig = {
Type = "simple";
StateDirectory = "ferretdb";
WorkingDirectory = "/var/lib/ferretdb";
ExecStart = "${cfg.package}/bin/ferretdb";
Restart = "on-failure";
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
PrivateDevices = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
DynamicUser = true;
};
};
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.influxdb;
@ -55,24 +60,32 @@ let
https-enabled = false;
};
graphite = [{
enabled = false;
}];
graphite = [
{
enabled = false;
}
];
udp = [{
enabled = false;
}];
udp = [
{
enabled = false;
}
];
collectd = [{
enabled = false;
typesdb = "${pkgs.collectd-data}/share/collectd/types.db";
database = "collectd_db";
bind-address = ":25826";
}];
collectd = [
{
enabled = false;
typesdb = "${pkgs.collectd-data}/share/collectd/types.db";
database = "collectd_db";
bind-address = ":25826";
}
];
opentsdb = [{
enabled = false;
}];
opentsdb = [
{
enabled = false;
}
];
continuous_queries = {
enabled = true;
@ -93,7 +106,7 @@ let
};
} cfg.extraConfig;
configFile = (pkgs.formats.toml {}).generate "config.toml" configOptions;
configFile = (pkgs.formats.toml { }).generate "config.toml" configOptions;
in
{
@ -130,14 +143,13 @@ in
};
extraConfig = lib.mkOption {
default = {};
default = { };
description = "Extra configuration options for influxdb";
type = lib.types.attrs;
};
};
};
###### implementation
config = lib.mkIf config.services.influxdb.enable {
@ -159,7 +171,9 @@ in
postStart =
let
scheme = if configOptions.http.https-enabled then "-k https" else "http";
bindAddr = (ba: if lib.hasPrefix ":" ba then "127.0.0.1${ba}" else "${ba}")(toString configOptions.http.bind-address);
bindAddr = (ba: if lib.hasPrefix ":" ba then "127.0.0.1${ba}" else "${ba}") (
toString configOptions.http.bind-address
);
in
lib.mkBefore ''
until ${pkgs.curl.bin}/bin/curl -s -o /dev/null ${scheme}://${bindAddr}/ping; do

View file

@ -1,49 +1,137 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.redis;
mkValueString = value:
if value == true then "yes"
else if value == false then "no"
else lib.generators.mkValueStringDefault { } value;
mkValueString =
value:
if value == true then
"yes"
else if value == false then
"no"
else
lib.generators.mkValueStringDefault { } value;
redisConfig = settings: pkgs.writeText "redis.conf" (lib.generators.toKeyValue {
listsAsDuplicateKeys = true;
mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
} settings);
redisConfig =
settings:
pkgs.writeText "redis.conf" (
lib.generators.toKeyValue {
listsAsDuplicateKeys = true;
mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
} settings
);
redisName = name: "redis" + lib.optionalString (name != "") ("-"+name);
redisName = name: "redis" + lib.optionalString (name != "") ("-" + name);
enabledServers = lib.filterAttrs (name: conf: conf.enable) config.services.redis.servers;
in {
in
{
imports = [
(lib.mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
(lib.mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
(lib.mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
(lib.mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
(lib.mkRemovedOptionModule [
"services"
"redis"
"user"
] "The redis module now is hardcoded to the redis user.")
(lib.mkRemovedOptionModule [
"services"
"redis"
"dbpath"
] "The redis module now uses /var/lib/redis as data directory.")
(lib.mkRemovedOptionModule [
"services"
"redis"
"dbFilename"
] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
(lib.mkRemovedOptionModule [
"services"
"redis"
"appendOnlyFilename"
] "This option was never used.")
(lib.mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
(lib.mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.servers.*.settings instead.")
(lib.mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ])
(lib.mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ])
(lib.mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ])
(lib.mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ])
(lib.mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ])
(lib.mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ])
(lib.mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ])
(lib.mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ])
(lib.mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ])
(lib.mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ])
(lib.mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ])
(lib.mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ])
(lib.mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ])
(lib.mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ])
(lib.mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ])
(lib.mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ])
(lib.mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ])
(lib.mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ])
(lib.mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ])
(lib.mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ])
(lib.mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ])
(lib.mkRemovedOptionModule [
"services"
"redis"
"extraConfig"
] "Use services.redis.servers.*.settings instead.")
(lib.mkRenamedOptionModule
[ "services" "redis" "enable" ]
[ "services" "redis" "servers" "" "enable" ]
)
(lib.mkRenamedOptionModule [ "services" "redis" "port" ] [ "services" "redis" "servers" "" "port" ])
(lib.mkRenamedOptionModule
[ "services" "redis" "openFirewall" ]
[ "services" "redis" "servers" "" "openFirewall" ]
)
(lib.mkRenamedOptionModule [ "services" "redis" "bind" ] [ "services" "redis" "servers" "" "bind" ])
(lib.mkRenamedOptionModule
[ "services" "redis" "unixSocket" ]
[ "services" "redis" "servers" "" "unixSocket" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "unixSocketPerm" ]
[ "services" "redis" "servers" "" "unixSocketPerm" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "logLevel" ]
[ "services" "redis" "servers" "" "logLevel" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "logfile" ]
[ "services" "redis" "servers" "" "logfile" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "syslog" ]
[ "services" "redis" "servers" "" "syslog" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "databases" ]
[ "services" "redis" "servers" "" "databases" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "maxclients" ]
[ "services" "redis" "servers" "" "maxclients" ]
)
(lib.mkRenamedOptionModule [ "services" "redis" "save" ] [ "services" "redis" "servers" "" "save" ])
(lib.mkRenamedOptionModule
[ "services" "redis" "slaveOf" ]
[ "services" "redis" "servers" "" "slaveOf" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "masterAuth" ]
[ "services" "redis" "servers" "" "masterAuth" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "requirePass" ]
[ "services" "redis" "servers" "" "requirePass" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "requirePassFile" ]
[ "services" "redis" "servers" "" "requirePassFile" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "appendOnly" ]
[ "services" "redis" "servers" "" "appendOnly" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "appendFsync" ]
[ "services" "redis" "servers" "" "appendFsync" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "slowLogLogSlowerThan" ]
[ "services" "redis" "servers" "" "slowLogLogSlowerThan" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "slowLogMaxLen" ]
[ "services" "redis" "servers" "" "slowLogMaxLen" ]
)
(lib.mkRenamedOptionModule
[ "services" "redis" "settings" ]
[ "services" "redis" "servers" "" "settings" ]
)
];
###### interface
@ -53,365 +141,435 @@ in {
services.redis = {
package = lib.mkPackageOption pkgs "redis" { };
vmOverCommit = lib.mkEnableOption ''
set `vm.overcommit_memory` sysctl to 1
(Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
'' // { default = true; };
vmOverCommit =
lib.mkEnableOption ''
set `vm.overcommit_memory` sysctl to 1
(Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
''
// {
default = true;
};
servers = lib.mkOption {
type = with lib.types; attrsOf (submodule ({ config, name, ... }: {
options = {
enable = lib.mkEnableOption "Redis server";
user = lib.mkOption {
type = types.str;
default = redisName name;
defaultText = lib.literalExpression ''
if name == "" then "redis" else "redis-''${name}"
'';
description = ''
User account under which this instance of redis-server runs.
::: {.note}
If left as the default value this user will automatically be
created on system activation, otherwise you are responsible for
ensuring the user exists before the redis service starts.
'';
};
group = lib.mkOption {
type = types.str;
default = config.user;
defaultText = lib.literalExpression "config.user";
description = ''
Group account under which this instance of redis-server runs.
::: {.note}
If left as the default value this group will automatically be
created on system activation, otherwise you are responsible for
ensuring the group exists before the redis service starts.
'';
};
port = lib.mkOption {
type = types.port;
default = if name == "" then 6379 else 0;
defaultText = lib.literalExpression ''if name == "" then 6379 else 0'';
description = ''
The TCP port to accept connections.
If port 0 is specified Redis will not listen on a TCP socket.
'';
};
openFirewall = lib.mkOption {
type = types.bool;
default = false;
description = ''
Whether to open ports in the firewall for the server.
'';
};
extraParams = lib.mkOption {
type = with types; listOf str;
default = [];
description = "Extra parameters to append to redis-server invocation";
example = [ "--sentinel" ];
};
bind = lib.mkOption {
type = with types; nullOr str;
default = "127.0.0.1";
description = ''
The IP interface to bind to.
`null` means "all interfaces".
'';
example = "192.0.2.1";
};
unixSocket = lib.mkOption {
type = with types; nullOr path;
default = "/run/${redisName name}/redis.sock";
defaultText = lib.literalExpression ''
if name == "" then "/run/redis/redis.sock" else "/run/redis-''${name}/redis.sock"
'';
description = "The path to the socket to bind to.";
};
unixSocketPerm = lib.mkOption {
type = types.int;
default = 660;
description = "Change permissions for the socket";
example = 600;
};
logLevel = lib.mkOption {
type = types.str;
default = "notice"; # debug, verbose, notice, warning
example = "debug";
description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
};
logfile = lib.mkOption {
type = types.str;
default = "/dev/null";
description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
example = "/var/log/redis.log";
};
syslog = lib.mkOption {
type = types.bool;
default = true;
description = "Enable logging to the system logger.";
};
databases = lib.mkOption {
type = types.int;
default = 16;
description = "Set the number of databases.";
};
maxclients = lib.mkOption {
type = types.int;
default = 10000;
description = "Set the max number of connected clients at the same time.";
};
save = lib.mkOption {
type = with types; listOf (listOf int);
default = [ [900 1] [300 10] [60 10000] ];
description = ''
The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.
If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence).
'';
};
slaveOf = lib.mkOption {
type = with types; nullOr (submodule ({ ... }: {
type =
with lib.types;
attrsOf (
submodule (
{ config, name, ... }:
{
options = {
ip = lib.mkOption {
type = str;
description = "IP of the Redis master";
example = "192.168.1.100";
enable = lib.mkEnableOption "Redis server";
user = lib.mkOption {
type = types.str;
default = redisName name;
defaultText = lib.literalExpression ''
if name == "" then "redis" else "redis-''${name}"
'';
description = ''
User account under which this instance of redis-server runs.
::: {.note}
If left as the default value this user will automatically be
created on system activation, otherwise you are responsible for
ensuring the user exists before the redis service starts.
'';
};
group = lib.mkOption {
type = types.str;
default = config.user;
defaultText = lib.literalExpression "config.user";
description = ''
Group account under which this instance of redis-server runs.
::: {.note}
If left as the default value this group will automatically be
created on system activation, otherwise you are responsible for
ensuring the group exists before the redis service starts.
'';
};
port = lib.mkOption {
type = port;
description = "port of the Redis master";
default = 6379;
type = types.port;
default = if name == "" then 6379 else 0;
defaultText = lib.literalExpression ''if name == "" then 6379 else 0'';
description = ''
The TCP port to accept connections.
If port 0 is specified Redis will not listen on a TCP socket.
'';
};
openFirewall = lib.mkOption {
type = types.bool;
default = false;
description = ''
Whether to open ports in the firewall for the server.
'';
};
extraParams = lib.mkOption {
type = with types; listOf str;
default = [ ];
description = "Extra parameters to append to redis-server invocation";
example = [ "--sentinel" ];
};
bind = lib.mkOption {
type = with types; nullOr str;
default = "127.0.0.1";
description = ''
The IP interface to bind to.
`null` means "all interfaces".
'';
example = "192.0.2.1";
};
unixSocket = lib.mkOption {
type = with types; nullOr path;
default = "/run/${redisName name}/redis.sock";
defaultText = lib.literalExpression ''
if name == "" then "/run/redis/redis.sock" else "/run/redis-''${name}/redis.sock"
'';
description = "The path to the socket to bind to.";
};
unixSocketPerm = lib.mkOption {
type = types.int;
default = 660;
description = "Change permissions for the socket";
example = 600;
};
logLevel = lib.mkOption {
type = types.str;
default = "notice"; # debug, verbose, notice, warning
example = "debug";
description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
};
logfile = lib.mkOption {
type = types.str;
default = "/dev/null";
description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
example = "/var/log/redis.log";
};
syslog = lib.mkOption {
type = types.bool;
default = true;
description = "Enable logging to the system logger.";
};
databases = lib.mkOption {
type = types.int;
default = 16;
description = "Set the number of databases.";
};
maxclients = lib.mkOption {
type = types.int;
default = 10000;
description = "Set the max number of connected clients at the same time.";
};
save = lib.mkOption {
type = with types; listOf (listOf int);
default = [
[
900
1
]
[
300
10
]
[
60
10000
]
];
description = ''
The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.
If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence).
'';
};
slaveOf = lib.mkOption {
type =
with types;
nullOr (
submodule (
{ ... }:
{
options = {
ip = lib.mkOption {
type = str;
description = "IP of the Redis master";
example = "192.168.1.100";
};
port = lib.mkOption {
type = port;
description = "port of the Redis master";
default = 6379;
};
};
}
)
);
default = null;
description = "IP and port to which this redis instance acts as a slave.";
example = {
ip = "192.168.1.100";
port = 6379;
};
};
masterAuth = lib.mkOption {
type = with types; nullOr str;
default = null;
description = ''
If the master is password protected (using the requirePass configuration)
it is possible to tell the slave to authenticate before starting the replication synchronization
process, otherwise the master will refuse the slave request.
(STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
};
requirePass = lib.mkOption {
type = with types; nullOr str;
default = null;
description = ''
Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
Use requirePassFile to store it outside of the nix store in a dedicated file.
'';
example = "letmein!";
};
requirePassFile = lib.mkOption {
type = with types; nullOr path;
default = null;
description = "File with password for the database.";
example = "/run/keys/redis-password";
};
appendOnly = lib.mkOption {
type = types.bool;
default = false;
description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
};
appendFsync = lib.mkOption {
type = types.str;
default = "everysec"; # no, always, everysec
description = "How often to fsync the append-only log, options: no, always, everysec.";
};
slowLogLogSlowerThan = lib.mkOption {
type = types.int;
default = 10000;
description = "Log queries whose execution take longer than X in milliseconds.";
example = 1000;
};
slowLogMaxLen = lib.mkOption {
type = types.int;
default = 128;
description = "Maximum number of items to keep in slow log.";
};
settings = lib.mkOption {
# TODO: this should be converted to freeformType
type =
with types;
attrsOf (oneOf [
bool
int
str
(listOf str)
]);
default = { };
description = ''
Redis configuration. Refer to
<https://redis.io/topics/config>
for details on supported values.
'';
example = lib.literalExpression ''
{
loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
}
'';
};
};
}));
default = null;
description = "IP and port to which this redis instance acts as a slave.";
example = { ip = "192.168.1.100"; port = 6379; };
};
masterAuth = lib.mkOption {
type = with types; nullOr str;
default = null;
description = ''
If the master is password protected (using the requirePass configuration)
it is possible to tell the slave to authenticate before starting the replication synchronization
process, otherwise the master will refuse the slave request.
(STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
};
requirePass = lib.mkOption {
type = with types; nullOr str;
default = null;
description = ''
Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
Use requirePassFile to store it outside of the nix store in a dedicated file.
'';
example = "letmein!";
};
requirePassFile = lib.mkOption {
type = with types; nullOr path;
default = null;
description = "File with password for the database.";
example = "/run/keys/redis-password";
};
appendOnly = lib.mkOption {
type = types.bool;
default = false;
description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
};
appendFsync = lib.mkOption {
type = types.str;
default = "everysec"; # no, always, everysec
description = "How often to fsync the append-only log, options: no, always, everysec.";
};
slowLogLogSlowerThan = lib.mkOption {
type = types.int;
default = 10000;
description = "Log queries whose execution take longer than X in milliseconds.";
example = 1000;
};
slowLogMaxLen = lib.mkOption {
type = types.int;
default = 128;
description = "Maximum number of items to keep in slow log.";
};
settings = lib.mkOption {
# TODO: this should be converted to freeformType
type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
default = {};
description = ''
Redis configuration. Refer to
<https://redis.io/topics/config>
for details on supported values.
'';
example = lib.literalExpression ''
{
loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
}
'';
};
};
config.settings = lib.mkMerge [
{
inherit (config) port logfile databases maxclients appendOnly;
daemonize = false;
supervised = "systemd";
loglevel = config.logLevel;
syslog-enabled = config.syslog;
save = if config.save == []
then ''""'' # Disable saving with `save = ""`
else map
(d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}")
config.save;
dbfilename = "dump.rdb";
dir = "/var/lib/${redisName name}";
appendfsync = config.appendFsync;
slowlog-log-slower-than = config.slowLogLogSlowerThan;
slowlog-max-len = config.slowLogMaxLen;
}
(lib.mkIf (config.bind != null) { inherit (config) bind; })
(lib.mkIf (config.unixSocket != null) {
unixsocket = config.unixSocket;
unixsocketperm = toString config.unixSocketPerm;
})
(lib.mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; })
(lib.mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
(lib.mkIf (config.requirePass != null) { requirepass = config.requirePass; })
];
}));
config.settings = lib.mkMerge [
{
inherit (config)
port
logfile
databases
maxclients
appendOnly
;
daemonize = false;
supervised = "systemd";
loglevel = config.logLevel;
syslog-enabled = config.syslog;
save =
if config.save == [ ] then
''""'' # Disable saving with `save = ""`
else
map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
dbfilename = "dump.rdb";
dir = "/var/lib/${redisName name}";
appendfsync = config.appendFsync;
slowlog-log-slower-than = config.slowLogLogSlowerThan;
slowlog-max-len = config.slowLogMaxLen;
}
(lib.mkIf (config.bind != null) { inherit (config) bind; })
(lib.mkIf (config.unixSocket != null) {
unixsocket = config.unixSocket;
unixsocketperm = toString config.unixSocketPerm;
})
(lib.mkIf (config.slaveOf != null) {
slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}";
})
(lib.mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
(lib.mkIf (config.requirePass != null) { requirepass = config.requirePass; })
];
}
)
);
description = "Configuration of multiple `redis-server` instances.";
default = {};
default = { };
};
};
};
###### implementation
config = lib.mkIf (enabledServers != {}) {
config = lib.mkIf (enabledServers != { }) {
assertions = lib.attrValues (lib.mapAttrs (name: conf: {
assertion = conf.requirePass != null -> conf.requirePassFile == null;
message = ''
You can only set one services.redis.servers.${name}.requirePass
or services.redis.servers.${name}.requirePassFile
'';
}) enabledServers);
assertions = lib.attrValues (
lib.mapAttrs (name: conf: {
assertion = conf.requirePass != null -> conf.requirePassFile == null;
message = ''
You can only set one services.redis.servers.${name}.requirePass
or services.redis.servers.${name}.requirePassFile
'';
}) enabledServers
);
boot.kernel.sysctl = lib.mkIf cfg.vmOverCommit {
"vm.overcommit_memory" = "1";
};
networking.firewall.allowedTCPPorts = lib.concatMap (conf:
lib.optional conf.openFirewall conf.port
networking.firewall.allowedTCPPorts = lib.concatMap (
conf: lib.optional conf.openFirewall conf.port
) (lib.attrValues enabledServers);
environment.systemPackages = [ cfg.package ];
users.users = lib.mapAttrs' (name: conf: lib.nameValuePair (redisName name) {
description = "System user for the redis-server instance ${name}";
isSystemUser = true;
group = redisName name;
}) enabledServers;
users.groups = lib.mapAttrs' (name: conf: lib.nameValuePair (redisName name) {
}) enabledServers;
users.users = lib.mapAttrs' (
name: conf:
lib.nameValuePair (redisName name) {
description = "System user for the redis-server instance ${name}";
isSystemUser = true;
group = redisName name;
}
) enabledServers;
users.groups = lib.mapAttrs' (
name: conf:
lib.nameValuePair (redisName name) {
}
) enabledServers;
systemd.services = lib.mapAttrs' (name: conf: lib.nameValuePair (redisName name) {
description = "Redis Server - ${redisName name}";
systemd.services = lib.mapAttrs' (
name: conf:
lib.nameValuePair (redisName name) {
description = "Redis Server - ${redisName name}";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/${cfg.package.serverBin or "redis-server"} /var/lib/${redisName name}/redis.conf ${lib.escapeShellArgs conf.extraParams}";
ExecStartPre = "+"+pkgs.writeShellScript "${redisName name}-prep-conf" (let
redisConfVar = "/var/lib/${redisName name}/redis.conf";
redisConfRun = "/run/${redisName name}/nixos.conf";
redisConfStore = redisConfig conf.settings;
in ''
touch "${redisConfVar}" "${redisConfRun}"
chown '${conf.user}':'${conf.group}' "${redisConfVar}" "${redisConfRun}"
chmod 0600 "${redisConfVar}" "${redisConfRun}"
if [ ! -s ${redisConfVar} ]; then
echo 'include "${redisConfRun}"' > "${redisConfVar}"
fi
echo 'include "${redisConfStore}"' > "${redisConfRun}"
${lib.optionalString (conf.requirePassFile != null) ''
{
echo -n "requirepass "
cat ${lib.escapeShellArg conf.requirePassFile}
} >> "${redisConfRun}"
''}
'');
Type = "notify";
# User and group
User = conf.user;
Group = conf.group;
# Runtime directory and mode
RuntimeDirectory = redisName name;
RuntimeDirectoryMode = "0750";
# State directory and mode
StateDirectory = redisName name;
StateDirectoryMode = "0700";
# Access write directories
UMask = "0077";
# Capabilities
CapabilityBoundingSet = "";
# Security
NoNewPrivileges = true;
# Process Properties
LimitNOFILE = lib.mkDefault "${toString (conf.maxclients + 32)}";
# Sandboxing
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
LockPersonality = true;
# we need to disable MemoryDenyWriteExecute for keydb
MemoryDenyWriteExecute = cfg.package.pname != "keydb";
RestrictRealtime = true;
RestrictSUIDSGID = true;
PrivateMounts = true;
# System Call Filtering
SystemCallArchitectures = "native";
SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";
};
}) enabledServers;
serviceConfig = {
ExecStart = "${cfg.package}/bin/${
cfg.package.serverBin or "redis-server"
} /var/lib/${redisName name}/redis.conf ${lib.escapeShellArgs conf.extraParams}";
ExecStartPre =
"+"
+ pkgs.writeShellScript "${redisName name}-prep-conf" (
let
redisConfVar = "/var/lib/${redisName name}/redis.conf";
redisConfRun = "/run/${redisName name}/nixos.conf";
redisConfStore = redisConfig conf.settings;
in
''
touch "${redisConfVar}" "${redisConfRun}"
chown '${conf.user}':'${conf.group}' "${redisConfVar}" "${redisConfRun}"
chmod 0600 "${redisConfVar}" "${redisConfRun}"
if [ ! -s ${redisConfVar} ]; then
echo 'include "${redisConfRun}"' > "${redisConfVar}"
fi
echo 'include "${redisConfStore}"' > "${redisConfRun}"
${lib.optionalString (conf.requirePassFile != null) ''
{
echo -n "requirepass "
cat ${lib.escapeShellArg conf.requirePassFile}
} >> "${redisConfRun}"
''}
''
);
Type = "notify";
# User and group
User = conf.user;
Group = conf.group;
# Runtime directory and mode
RuntimeDirectory = redisName name;
RuntimeDirectoryMode = "0750";
# State directory and mode
StateDirectory = redisName name;
StateDirectoryMode = "0700";
# Access write directories
UMask = "0077";
# Capabilities
CapabilityBoundingSet = "";
# Security
NoNewPrivileges = true;
# Process Properties
LimitNOFILE = lib.mkDefault "${toString (conf.maxclients + 32)}";
# Sandboxing
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
LockPersonality = true;
# we need to disable MemoryDenyWriteExecute for keydb
MemoryDenyWriteExecute = cfg.package.pname != "keydb";
RestrictRealtime = true;
RestrictSUIDSGID = true;
PrivateMounts = true;
# System Call Filtering
SystemCallArchitectures = "native";
SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";
};
}
) enabledServers;
};
}

View file

@ -1,5 +1,10 @@
# Bamf
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
{
meta = with lib; {
maintainers = with lib.maintainers; [ ];

View file

@ -1,5 +1,10 @@
# rygel service.
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let

View file

@ -1,5 +1,10 @@
# Accounts-SSO gSignOn daemon
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
package = pkgs.gsignond.override { plugins = config.services.gsignond.plugins; };
in
@ -24,7 +29,7 @@ in
plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [];
default = [ ];
description = ''
What plugins to use with the gSignOn daemon.
'';

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
inherit (builtins) concatMap;
@ -8,7 +13,13 @@ let
inherit (lib.modules) mkIf;
inherit (lib.options) literalExpression mkOption;
inherit (lib.strings) concatStringsSep makeSearchPath;
inherit (lib.types) bool listOf attrsOf package lines;
inherit (lib.types)
bool
listOf
attrsOf
package
lines
;
inherit (lib.path) subpath;
pwCfg = config.services.pipewire;
@ -17,24 +28,29 @@ let
json = pkgs.formats.json { };
configSectionsToConfFile = path: value:
pkgs.writeTextDir
path
(concatStringsSep "\n" (
mapAttrsToList
(section: content: "${section} = " + (builtins.toJSON content))
value
));
configSectionsToConfFile =
path: value:
pkgs.writeTextDir path (
concatStringsSep "\n" (
mapAttrsToList (section: content: "${section} = " + (builtins.toJSON content)) value
)
);
mapConfigToFiles = config:
mapAttrsToList
(name: value: configSectionsToConfFile "share/wireplumber/wireplumber.conf.d/${name}.conf" value)
config;
mapConfigToFiles =
config:
mapAttrsToList (
name: value: configSectionsToConfFile "share/wireplumber/wireplumber.conf.d/${name}.conf" value
) config;
mapScriptsToFiles = scripts:
mapAttrsToList
(relativePath: value: pkgs.writeTextDir (subpath.join ["share/wireplumber/scripts" relativePath]) value)
scripts;
mapScriptsToFiles =
scripts:
mapAttrsToList (
relativePath: value:
pkgs.writeTextDir (subpath.join [
"share/wireplumber/scripts"
relativePath
]) value
) scripts;
in
{
meta.maintainers = [ maintainers.k900 ];
@ -62,34 +78,34 @@ in
type = attrsOf (attrsOf json.type);
default = { };
example = literalExpression ''
{
"log-level-debug" = {
"context.properties" = {
# Output Debug log messages as opposed to only the default level (Notice)
"log.level" = "D";
{
"log-level-debug" = {
"context.properties" = {
# Output Debug log messages as opposed to only the default level (Notice)
"log.level" = "D";
};
};
};
"wh-1000xm3-ldac-hq" = {
"monitor.bluez.rules" = [
{
matches = [
{
# Match any bluetooth device with ids equal to that of a WH-1000XM3
"device.name" = "~bluez_card.*";
"device.product.id" = "0x0cd3";
"device.vendor.id" = "usb:054c";
}
];
actions = {
update-props = {
# Set quality to high quality instead of the default of auto
"bluez5.a2dp.ldac.quality" = "hq";
"wh-1000xm3-ldac-hq" = {
"monitor.bluez.rules" = [
{
matches = [
{
# Match any bluetooth device with ids equal to that of a WH-1000XM3
"device.name" = "~bluez_card.*";
"device.product.id" = "0x0cd3";
"device.vendor.id" = "usb:054c";
}
];
actions = {
update-props = {
# Set quality to high quality instead of the default of auto
"bluez5.a2dp.ldac.quality" = "hq";
};
};
};
}
];
};
}
}
];
};
}
'';
description = ''
Additional configuration for the WirePlumber daemon when run in
@ -169,16 +185,16 @@ in
type = listOf package;
default = [ ];
example = literalExpression ''
[
(pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" '''
monitor.bluez.properties = {
bluez5.roles = [ a2dp_sink a2dp_source bap_sink bap_source hsp_hs hsp_ag hfp_hf hfp_ag ]
bluez5.codecs = [ sbc sbc_xq aac ]
bluez5.enable-sbc-xq = true
bluez5.hfphsp-backend = "native"
}
''')
]
[
(pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" '''
monitor.bluez.properties = {
bluez5.roles = [ a2dp_sink a2dp_source bap_sink bap_source hsp_hs hsp_ag hfp_hf hfp_ag ]
bluez5.codecs = [ sbc sbc_xq aac ]
bluez5.enable-sbc-xq = true
bluez5.hfphsp-backend = "native"
}
''')
]
'';
description = ''
List of packages that provide WirePlumber configuration, in the form of
@ -231,8 +247,12 @@ in
pathsToLink = [ "/share/wireplumber/scripts" ];
};
configPackages = cfg.configPackages
++ [ extraConfigPkg extraScriptsPkg ]
configPackages =
cfg.configPackages
++ [
extraConfigPkg
extraScriptsPkg
]
++ optional (!pwUsedForAudio) pwNotForAudioConfigPkg;
configs = pkgs.buildEnv {
@ -241,14 +261,9 @@ in
pathsToLink = [ "/share/wireplumber" ];
};
requiredLv2Packages = flatten
(
concatMap
(p:
attrByPath [ "passthru" "requiredLv2Packages" ] [ ] p
)
configPackages
);
requiredLv2Packages = flatten (
concatMap (p: attrByPath [ "passthru" "requiredLv2Packages" ] [ ] p) configPackages
);
lv2Plugins = pkgs.buildEnv {
name = "wireplumber-lv2-plugins";
@ -280,12 +295,18 @@ in
# Make WirePlumber find our config/script files and lv2 plugins required by those
# (but also the configs/scripts shipped with WirePlumber)
XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
XDG_DATA_DIRS = makeSearchPath "share" [
configs
cfg.package
];
LV2_PATH = "${lv2Plugins}/lib/lv2";
};
systemd.user.services.wireplumber.environment = mkIf (!pwCfg.systemWide) {
XDG_DATA_DIRS = makeSearchPath "share" [ configs cfg.package ];
XDG_DATA_DIRS = makeSearchPath "share" [
configs
cfg.package
];
LV2_PATH = "${lv2Plugins}/lib/lv2";
};
};

View file

@ -1,148 +1,151 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.athens;
athensConfig = lib.flip lib.recursiveUpdate cfg.extraConfig (
{
GoBinary = "${cfg.goBinary}/bin/go";
GoEnv = cfg.goEnv;
GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars;
GoGetWorkers = cfg.goGetWorkers;
GoGetDir = cfg.goGetDir;
ProtocolWorkers = cfg.protocolWorkers;
LogLevel = cfg.logLevel;
CloudRuntime = cfg.cloudRuntime;
EnablePprof = cfg.enablePprof;
PprofPort = ":${toString cfg.pprofPort}";
FilterFile = cfg.filterFile;
RobotsFile = cfg.robotsFile;
Timeout = cfg.timeout;
StorageType = cfg.storageType;
TLSCertFile = cfg.tlsCertFile;
TLSKeyFile = cfg.tlsKeyFile;
Port = ":${toString cfg.port}";
UnixSocket = cfg.unixSocket;
GlobalEndpoint = cfg.globalEndpoint;
BasicAuthUser = cfg.basicAuthUser;
BasicAuthPass = cfg.basicAuthPass;
ForceSSL = cfg.forceSSL;
ValidatorHook = cfg.validatorHook;
PathPrefix = cfg.pathPrefix;
NETRCPath = cfg.netrcPath;
GithubToken = cfg.githubToken;
HGRCPath = cfg.hgrcPath;
TraceExporter = cfg.traceExporter;
StatsExporter = cfg.statsExporter;
SumDBs = cfg.sumDBs;
NoSumPatterns = cfg.noSumPatterns;
DownloadMode = cfg.downloadMode;
NetworkMode = cfg.networkMode;
DownloadURL = cfg.downloadURL;
SingleFlightType = cfg.singleFlightType;
IndexType = cfg.indexType;
ShutdownTimeout = cfg.shutdownTimeout;
SingleFlight = {
Etcd = {
Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints;
};
Redis = {
Endpoint = cfg.singleFlight.redis.endpoint;
Password = cfg.singleFlight.redis.password;
LockConfig = {
TTL = cfg.singleFlight.redis.lockConfig.ttl;
Timeout = cfg.singleFlight.redis.lockConfig.timeout;
MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries;
};
};
RedisSentinel = {
Endpoints = cfg.singleFlight.redisSentinel.endpoints;
MasterName = cfg.singleFlight.redisSentinel.masterName;
SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword;
LockConfig = {
TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl;
Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout;
MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries;
};
athensConfig = lib.flip lib.recursiveUpdate cfg.extraConfig ({
GoBinary = "${cfg.goBinary}/bin/go";
GoEnv = cfg.goEnv;
GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars;
GoGetWorkers = cfg.goGetWorkers;
GoGetDir = cfg.goGetDir;
ProtocolWorkers = cfg.protocolWorkers;
LogLevel = cfg.logLevel;
CloudRuntime = cfg.cloudRuntime;
EnablePprof = cfg.enablePprof;
PprofPort = ":${toString cfg.pprofPort}";
FilterFile = cfg.filterFile;
RobotsFile = cfg.robotsFile;
Timeout = cfg.timeout;
StorageType = cfg.storageType;
TLSCertFile = cfg.tlsCertFile;
TLSKeyFile = cfg.tlsKeyFile;
Port = ":${toString cfg.port}";
UnixSocket = cfg.unixSocket;
GlobalEndpoint = cfg.globalEndpoint;
BasicAuthUser = cfg.basicAuthUser;
BasicAuthPass = cfg.basicAuthPass;
ForceSSL = cfg.forceSSL;
ValidatorHook = cfg.validatorHook;
PathPrefix = cfg.pathPrefix;
NETRCPath = cfg.netrcPath;
GithubToken = cfg.githubToken;
HGRCPath = cfg.hgrcPath;
TraceExporter = cfg.traceExporter;
StatsExporter = cfg.statsExporter;
SumDBs = cfg.sumDBs;
NoSumPatterns = cfg.noSumPatterns;
DownloadMode = cfg.downloadMode;
NetworkMode = cfg.networkMode;
DownloadURL = cfg.downloadURL;
SingleFlightType = cfg.singleFlightType;
IndexType = cfg.indexType;
ShutdownTimeout = cfg.shutdownTimeout;
SingleFlight = {
Etcd = {
Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints;
};
Redis = {
Endpoint = cfg.singleFlight.redis.endpoint;
Password = cfg.singleFlight.redis.password;
LockConfig = {
TTL = cfg.singleFlight.redis.lockConfig.ttl;
Timeout = cfg.singleFlight.redis.lockConfig.timeout;
MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries;
};
};
Storage = {
CDN = {
Endpoint = cfg.storage.cdn.endpoint;
};
Disk = {
RootPath = cfg.storage.disk.rootPath;
};
GCP = {
ProjectID = cfg.storage.gcp.projectID;
Bucket = cfg.storage.gcp.bucket;
JSONKey = cfg.storage.gcp.jsonKey;
};
Minio = {
Endpoint = cfg.storage.minio.endpoint;
Key = cfg.storage.minio.key;
Secret = cfg.storage.minio.secret;
EnableSSL = cfg.storage.minio.enableSSL;
Bucket = cfg.storage.minio.bucket;
region = cfg.storage.minio.region;
};
Mongo = {
URL = cfg.storage.mongo.url;
DefaultDBName = cfg.storage.mongo.defaultDBName;
CertPath = cfg.storage.mongo.certPath;
Insecure = cfg.storage.mongo.insecure;
};
S3 = {
Region = cfg.storage.s3.region;
Key = cfg.storage.s3.key;
Secret = cfg.storage.s3.secret;
Token = cfg.storage.s3.token;
Bucket = cfg.storage.s3.bucket;
ForcePathStyle = cfg.storage.s3.forcePathStyle;
UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration;
CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint;
AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI;
Endpoint = cfg.storage.s3.endpoint;
};
AzureBlob = {
AccountName = cfg.storage.azureblob.accountName;
AccountKey = cfg.storage.azureblob.accountKey;
ContainerName = cfg.storage.azureblob.containerName;
};
External = {
URL = cfg.storage.external.url;
RedisSentinel = {
Endpoints = cfg.singleFlight.redisSentinel.endpoints;
MasterName = cfg.singleFlight.redisSentinel.masterName;
SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword;
LockConfig = {
TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl;
Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout;
MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries;
};
};
Index = {
MySQL = {
Protocol = cfg.index.mysql.protocol;
Host = cfg.index.mysql.host;
Port = cfg.index.mysql.port;
User = cfg.index.mysql.user;
Password = cfg.index.mysql.password;
Database = cfg.index.mysql.database;
Params = {
parseTime = cfg.index.mysql.params.parseTime;
timeout = cfg.index.mysql.params.timeout;
};
};
Postgres = {
Host = cfg.index.postgres.host;
Port = cfg.index.postgres.port;
User = cfg.index.postgres.user;
Password = cfg.index.postgres.password;
Database = cfg.index.postgres.database;
Params = {
connect_timeout = cfg.index.postgres.params.connect_timeout;
sslmode = cfg.index.postgres.params.sslmode;
};
};
Storage = {
CDN = {
Endpoint = cfg.storage.cdn.endpoint;
};
Disk = {
RootPath = cfg.storage.disk.rootPath;
};
GCP = {
ProjectID = cfg.storage.gcp.projectID;
Bucket = cfg.storage.gcp.bucket;
JSONKey = cfg.storage.gcp.jsonKey;
};
Minio = {
Endpoint = cfg.storage.minio.endpoint;
Key = cfg.storage.minio.key;
Secret = cfg.storage.minio.secret;
EnableSSL = cfg.storage.minio.enableSSL;
Bucket = cfg.storage.minio.bucket;
region = cfg.storage.minio.region;
};
Mongo = {
URL = cfg.storage.mongo.url;
DefaultDBName = cfg.storage.mongo.defaultDBName;
CertPath = cfg.storage.mongo.certPath;
Insecure = cfg.storage.mongo.insecure;
};
S3 = {
Region = cfg.storage.s3.region;
Key = cfg.storage.s3.key;
Secret = cfg.storage.s3.secret;
Token = cfg.storage.s3.token;
Bucket = cfg.storage.s3.bucket;
ForcePathStyle = cfg.storage.s3.forcePathStyle;
UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration;
CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint;
AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI;
Endpoint = cfg.storage.s3.endpoint;
};
AzureBlob = {
AccountName = cfg.storage.azureblob.accountName;
AccountKey = cfg.storage.azureblob.accountKey;
ContainerName = cfg.storage.azureblob.containerName;
};
External = {
URL = cfg.storage.external.url;
};
};
Index = {
MySQL = {
Protocol = cfg.index.mysql.protocol;
Host = cfg.index.mysql.host;
Port = cfg.index.mysql.port;
User = cfg.index.mysql.user;
Password = cfg.index.mysql.password;
Database = cfg.index.mysql.database;
Params = {
parseTime = cfg.index.mysql.params.parseTime;
timeout = cfg.index.mysql.params.timeout;
};
};
}
);
Postgres = {
Host = cfg.index.postgres.host;
Port = cfg.index.postgres.port;
User = cfg.index.postgres.user;
Password = cfg.index.postgres.password;
Database = cfg.index.postgres.database;
Params = {
connect_timeout = cfg.index.postgres.params.connect_timeout;
sslmode = cfg.index.postgres.params.sslmode;
};
};
};
});
configFile = lib.pipe athensConfig [
(lib.filterAttrsRecursive (_k: v: v != null))
((pkgs.formats.toml {}).generate "config.toml")
((pkgs.formats.toml { }).generate "config.toml")
];
in
{
@ -177,7 +180,10 @@ in
};
goEnv = lib.mkOption {
type = lib.types.enum [ "development" "production" ];
type = lib.types.enum [
"development"
"production"
];
description = "Specifies the type of environment to run. One of 'development' or 'production'.";
default = "development";
example = "production";
@ -220,7 +226,17 @@ in
};
logLevel = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [ "panic" "fatal" "error" "warning" "info" "debug" "trace" ]);
type = lib.types.nullOr (
lib.types.enum [
"panic"
"fatal"
"error"
"warning"
"info"
"debug"
"trace"
]
);
description = ''
Log level for Athens.
Supports all logrus log levels (https://github.com/Sirupsen/logrus#level-logging)".
@ -230,7 +246,10 @@ in
};
cloudRuntime = lib.mkOption {
type = lib.types.enum [ "GCP" "none" ];
type = lib.types.enum [
"GCP"
"none"
];
description = ''
Specifies the Cloud Provider on which the Proxy/registry is running.
'';
@ -279,7 +298,16 @@ in
};
storageType = lib.mkOption {
type = lib.types.enum [ "memory" "disk" "mongo" "gcp" "minio" "s3" "azureblob" "external" ];
type = lib.types.enum [
"memory"
"disk"
"mongo"
"gcp"
"minio"
"s3"
"azureblob"
"external"
];
description = "Specifies the type of storage backend to use.";
default = "disk";
};
@ -401,7 +429,12 @@ in
};
traceExporter = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [ "jaeger" "datadog" ]);
type = lib.types.nullOr (
lib.types.enum [
"jaeger"
"datadog"
]
);
description = ''
Trace exporter to use.
'';
@ -442,7 +475,16 @@ in
};
downloadMode = lib.mkOption {
type = lib.types.oneOf [ (lib.types.enum [ "sync" "async" "redirect" "async_redirect" "none" ]) (lib.types.strMatching "^file:.*$|^custom:.*$") ];
type = lib.types.oneOf [
(lib.types.enum [
"sync"
"async"
"redirect"
"async_redirect"
"none"
])
(lib.types.strMatching "^file:.*$|^custom:.*$")
];
description = ''
Defines how Athens behaves when a module@version
is not found in storage. There are 7 options:
@ -466,7 +508,11 @@ in
};
networkMode = lib.mkOption {
type = lib.types.enum [ "strict" "offline" "fallback" ];
type = lib.types.enum [
"strict"
"offline"
"fallback"
];
description = ''
Configures how Athens will return the results
of the /list endpoint as it can be assembled from both its own
@ -492,7 +538,14 @@ in
};
singleFlightType = lib.mkOption {
type = lib.types.enum [ "memory" "etcd" "redis" "redis-sentinel" "gcp" "azureblob" ];
type = lib.types.enum [
"memory"
"etcd"
"redis"
"redis-sentinel"
"gcp"
"azureblob"
];
description = ''
Determines what mechanism Athens uses to manage concurrency flowing into the Athens backend.
'';
@ -500,7 +553,12 @@ in
};
indexType = lib.mkOption {
type = lib.types.enum [ "none" "memory" "mysql" "postgres" ];
type = lib.types.enum [
"none"
"memory"
"mysql"
"postgres"
];
description = ''
Type of index backend Athens will use.
'';
@ -913,8 +971,12 @@ in
ProtectHome = "read-only";
ProtectSystem = "full";
ReadWritePaths = lib.mkIf (cfg.storage.disk.rootPath != null && (! lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath)) [ cfg.storage.disk.rootPath ];
StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [ (lib.removePrefix "/var/lib/" cfg.storage.disk.rootPath) ];
ReadWritePaths = lib.mkIf (
cfg.storage.disk.rootPath != null && (!lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath)
) [ cfg.storage.disk.rootPath ];
StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [
(lib.removePrefix "/var/lib/" cfg.storage.disk.rootPath)
];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
@ -923,7 +985,8 @@ in
};
networking.firewall = {
allowedTCPPorts = lib.optionals (cfg.unixSocket == null) [ cfg.port ]
allowedTCPPorts =
lib.optionals (cfg.unixSocket == null) [ cfg.port ]
++ lib.optionals cfg.enablePprof [ cfg.pprofPort ];
};
};

View file

@ -1,13 +1,18 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.jupyterhub;
kernels = (pkgs.jupyter-kernel.create {
definitions = if cfg.kernels != null
then cfg.kernels
else pkgs.jupyter-kernel.default;
});
kernels = (
pkgs.jupyter-kernel.create {
definitions = if cfg.kernels != null then cfg.kernels else pkgs.jupyter-kernel.default;
}
);
jupyterhubConfig = pkgs.writeText "jupyterhub_config.py" ''
c.JupyterHub.bind_url = "http://${cfg.host}:${toString cfg.port}"
@ -23,7 +28,8 @@ let
${cfg.extraConfig}
'';
in {
in
{
meta.maintainers = with lib.maintainers; [ costrouc ];
options.services.jupyterhub = {
@ -71,10 +77,12 @@ in {
jupyterhubEnv = lib.mkOption {
type = lib.types.package;
default = pkgs.python3.withPackages (p: with p; [
jupyterhub
jupyterhub-systemdspawner
]);
default = pkgs.python3.withPackages (
p: with p; [
jupyterhub
jupyterhub-systemdspawner
]
);
defaultText = lib.literalExpression ''
pkgs.python3.withPackages (p: with p; [
jupyterhub
@ -93,10 +101,12 @@ in {
jupyterlabEnv = lib.mkOption {
type = lib.types.package;
default = pkgs.python3.withPackages (p: with p; [
jupyterhub
jupyterlab
]);
default = pkgs.python3.withPackages (
p: with p; [
jupyterhub
jupyterlab
]
);
defaultText = lib.literalExpression ''
pkgs.python3.withPackages (p: with p; [
jupyterhub
@ -115,9 +125,15 @@ in {
};
kernels = lib.mkOption {
type = lib.types.nullOr (lib.types.attrsOf(lib.types.submodule (import ../jupyter/kernel-options.nix {
inherit lib pkgs;
})));
type = lib.types.nullOr (
lib.types.attrsOf (
lib.types.submodule (
import ../jupyter/kernel-options.nix {
inherit lib pkgs;
}
)
)
);
default = null;
example = lib.literalExpression ''
@ -179,7 +195,7 @@ in {
};
config = lib.mkMerge [
(lib.mkIf cfg.enable {
(lib.mkIf cfg.enable {
systemd.services.jupyterhub = {
description = "Jupyterhub development server";

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.greetd;
tty = "tty${toString cfg.vt}";
@ -79,12 +84,14 @@ in
Wants = [
"systemd-user-sessions.service"
];
After = [
"systemd-user-sessions.service"
"getty@${tty}.service"
] ++ lib.optionals (!cfg.greeterManagesPlymouth) [
"plymouth-quit-wait.service"
];
After =
[
"systemd-user-sessions.service"
"getty@${tty}.service"
]
++ lib.optionals (!cfg.greeterManagesPlymouth) [
"plymouth-quit-wait.service"
];
Conflicts = [
"getty@${tty}.service"
];

View file

@ -87,6 +87,7 @@
config.services.taler.enable && (config.services.taler.settings.taler ? CURRENCY)
) config.services.taler.settings.taler.CURRENCY;
services.libeufin.bank.settings.libeufin-bankdb-postgres.CONFIG = lib.mkIf config.services.libeufin.bank.createLocalDatabase "postgresql:///libeufin-bank";
services.libeufin.bank.settings.libeufin-bankdb-postgres.CONFIG =
lib.mkIf config.services.libeufin.bank.createLocalDatabase "postgresql:///libeufin-bank";
};
}

View file

@ -1,31 +1,45 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.archisteamfarm;
format = pkgs.formats.json { };
configFile = format.generate "ASF.json" (cfg.settings // {
# we disable it because ASF cannot update itself anyways
# and nixos takes care of restarting the service
# is in theory not needed as this is already the default for default builds
UpdateChannel = 0;
Headless = true;
} // lib.optionalAttrs (cfg.ipcPasswordFile != null) {
IPCPassword = "#ipcPassword#";
});
configFile = format.generate "ASF.json" (
cfg.settings
// {
# we disable it because ASF cannot update itself anyways
# and nixos takes care of restarting the service
# is in theory not needed as this is already the default for default builds
UpdateChannel = 0;
Headless = true;
}
// lib.optionalAttrs (cfg.ipcPasswordFile != null) {
IPCPassword = "#ipcPassword#";
}
);
ipc-config = format.generate "IPC.config" cfg.ipcSettings;
mkBot = n: c:
format.generate "${n}.json" (c.settings // {
SteamLogin = if c.username == "" then n else c.username;
Enabled = c.enabled;
} // lib.optionalAttrs (c.passwordFile != null) {
SteamPassword = c.passwordFile;
# sets the password format to file (https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Security#file)
PasswordFormat = 4;
});
mkBot =
n: c:
format.generate "${n}.json" (
c.settings
// {
SteamLogin = if c.username == "" then n else c.username;
Enabled = c.enabled;
}
// lib.optionalAttrs (c.passwordFile != null) {
SteamPassword = c.passwordFile;
# sets the password format to file (https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Security#file)
PasswordFormat = 4;
}
);
in
{
options.services.archisteamfarm = {
@ -120,35 +134,37 @@ in
};
bots = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
username = lib.mkOption {
type = lib.types.str;
description = "Name of the user to log in. Default is attribute name.";
default = "";
type = lib.types.attrsOf (
lib.types.submodule {
options = {
username = lib.mkOption {
type = lib.types.str;
description = "Name of the user to log in. Default is attribute name.";
default = "";
};
passwordFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.
Omit or set to null to provide the password a different way, such as through the web-ui.
'';
};
enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable the bot on startup.";
};
settings = lib.mkOption {
type = lib.types.attrs;
description = ''
Additional settings that are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config).
'';
default = { };
};
};
passwordFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.
Omit or set to null to provide the password a different way, such as through the web-ui.
'';
};
enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable the bot on startup.";
};
settings = lib.mkOption {
type = lib.types.attrs;
description = ''
Additional settings that are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config).
'';
default = { };
};
};
});
}
);
description = ''
Bots name and configuration.
'';
@ -156,7 +172,9 @@ in
exampleBot = {
username = "alice";
passwordFile = "/var/lib/archisteamfarm/secrets/password";
settings = { SteamParentalCode = "1234"; };
settings = {
SteamParentalCode = "1234";
};
};
};
default = { };
@ -221,24 +239,33 @@ in
RestrictSUIDSGID = true;
SecureBits = "noroot-locked";
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "mincore" ];
SystemCallFilter = [
"@system-service"
"~@privileged"
"mincore"
];
UMask = "0077";
}
];
preStart =
let
createBotsScript = pkgs.runCommand "ASF-bots" {
preferLocalBuild = true;
} ''
mkdir -p $out
# clean potential removed bots
rm -rf $out/*.json
for i in ${lib.concatStringsSep " " (map (x: "${lib.getName x},${x}") (lib.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
set -- $i
ln -fs $2 $out/$1
done
'';
createBotsScript =
pkgs.runCommand "ASF-bots"
{
preferLocalBuild = true;
}
''
mkdir -p $out
# clean potential removed bots
rm -rf $out/*.json
for i in ${
lib.concatStringsSep " " (map (x: "${lib.getName x},${x}") (lib.mapAttrsToList mkBot cfg.bots))
}; do IFS=",";
set -- $i
ln -fs $2 $out/$1
done
'';
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
in
''
@ -250,11 +277,11 @@ in
${replaceSecretBin} '#ipcPassword#' '${cfg.ipcPasswordFile}' config/ASF.json
''}
${lib.optionalString (cfg.ipcSettings != {}) ''
${lib.optionalString (cfg.ipcSettings != { }) ''
ln -fs ${ipc-config} config/IPC.config
''}
${lib.optionalString (cfg.bots != {}) ''
${lib.optionalString (cfg.bots != { }) ''
ln -fs ${createBotsScript}/* config/
''}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.factorio;
name = "Factorio";
@ -36,9 +41,11 @@ let
} // cfg.extraSettings;
serverSettingsString = builtins.toJSON (lib.filterAttrsRecursive (n: v: v != null) serverSettings);
serverSettingsFile = pkgs.writeText "server-settings.json" serverSettingsString;
playerListOption = name: list:
lib.optionalString (list != [])
"--${name}=${pkgs.writeText "${name}.json" (builtins.toJSON list)}";
playerListOption =
name: list:
lib.optionalString (
list != [ ]
) "--${name}=${pkgs.writeText "${name}.json" (builtins.toJSON list)}";
modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods cfg.mods-dat;
in
{
@ -67,8 +74,11 @@ in
# --use-server-whitelist) so we can't implement that behaviour, so we
# might as well match theirs.
type = lib.types.listOf lib.types.str;
default = [];
example = [ "Rseding91" "Oxyd" ];
default = [ ];
example = [
"Rseding91"
"Oxyd"
];
description = ''
If non-empty, only these player names are allowed to connect. The game
will not be able to save any changes made in-game with the /whitelist
@ -87,7 +97,7 @@ in
admins = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
default = [ ];
example = [ "username" ];
description = ''
List of player names which will be admin.
@ -167,7 +177,7 @@ in
};
mods = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [];
default = [ ];
description = ''
Mods the server should install and activate.
@ -202,8 +212,10 @@ in
};
extraSettings = lib.mkOption {
type = lib.types.attrs;
default = {};
example = { max_players = 64; };
default = { };
example = {
max_players = 64;
};
description = ''
Extra game configuration that will go into server-settings.json
'';
@ -288,9 +300,9 @@ in
config = lib.mkIf cfg.enable {
systemd.services.factorio = {
description = "Factorio headless server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
description = "Factorio headless server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
preStart =
(toString [
@ -299,11 +311,13 @@ in
"${cfg.package}/bin/factorio"
"--config=${cfg.configFile}"
"--create=${mkSavePath cfg.saveName}"
(lib.optionalString (cfg.mods != []) "--mod-directory=${modDir}")
(lib.optionalString (cfg.mods != [ ]) "--mod-directory=${modDir}")
])
+ (lib.optionalString (cfg.extraSettingsFile != null) ("\necho ${lib.strings.escapeShellArg serverSettingsString}"
+ (lib.optionalString (cfg.extraSettingsFile != null) (
"\necho ${lib.strings.escapeShellArg serverSettingsString}"
+ " \"$(cat ${cfg.extraSettingsFile})\" | ${lib.getExe pkgs.jq} -s add"
+ " > ${stateDir}/server-settings.json"));
+ " > ${stateDir}/server-settings.json"
));
serviceConfig = {
Restart = "always";
@ -318,15 +332,13 @@ in
"--bind=${cfg.bind}"
(lib.optionalString (!cfg.loadLatestSave) "--start-server=${mkSavePath cfg.saveName}")
"--server-settings=${
if (cfg.extraSettingsFile != null)
then "${stateDir}/server-settings.json"
else serverSettingsFile
if (cfg.extraSettingsFile != null) then "${stateDir}/server-settings.json" else serverSettingsFile
}"
(lib.optionalString cfg.loadLatestSave "--start-server-load-latest")
(lib.optionalString (cfg.mods != []) "--mod-directory=${modDir}")
(lib.optionalString (cfg.mods != [ ]) "--mod-directory=${modDir}")
(playerListOption "server-adminlist" cfg.admins)
(playerListOption "server-whitelist" cfg.allowedPlayers)
(lib.optionalString (cfg.allowedPlayers != []) "--use-server-whitelist")
(lib.optionalString (cfg.allowedPlayers != [ ]) "--use-server-whitelist")
];
# Sandboxing
@ -338,7 +350,12 @@ in
ProtectControlGroups = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictRealtime = true;
RestrictNamespaces = true;
MemoryDenyWriteExecute = true;

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.minecraft-server;
@ -8,16 +13,25 @@ let
eula=true
'';
whitelistFile = pkgs.writeText "whitelist.json"
(builtins.toJSON
(lib.mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
whitelistFile = pkgs.writeText "whitelist.json" (
builtins.toJSON (
lib.mapAttrsToList (n: v: {
name = n;
uuid = v;
}) cfg.whitelist
)
);
cfgToString = v: if builtins.isBool v then lib.boolToString v else toString v;
serverPropertiesFile = pkgs.writeText "server.properties" (''
# server.properties managed by NixOS configuration
'' + lib.concatStringsSep "\n" (lib.mapAttrsToList
(n: v: "${n}=${cfgToString v}") cfg.serverProperties));
serverPropertiesFile = pkgs.writeText "server.properties" (
''
# server.properties managed by NixOS configuration
''
+ lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: "${n}=${cfgToString v}") cfg.serverProperties
)
);
stopScript = pkgs.writeShellScript "minecraft-server-stop" ''
echo stop > ${config.systemd.sockets.minecraft-server.socketConfig.ListenFIFO}
@ -36,15 +50,20 @@ let
serverPort = cfg.serverProperties.server-port or defaultServerPort;
rconPort = if cfg.serverProperties.enable-rcon or false
then cfg.serverProperties."rcon.port" or 25575
else null;
rconPort =
if cfg.serverProperties.enable-rcon or false then
cfg.serverProperties."rcon.port" or 25575
else
null;
queryPort = if cfg.serverProperties.enable-query or false
then cfg.serverProperties."query.port" or 25565
else null;
queryPort =
if cfg.serverProperties.enable-query or false then
cfg.serverProperties."query.port" or 25565
else
null;
in {
in
{
options = {
services.minecraft-server = {
@ -98,13 +117,16 @@ in {
};
whitelist = lib.mkOption {
type = let
minecraftUUID = lib.types.strMatching
"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
description = "Minecraft UUID";
};
in lib.types.attrsOf minecraftUUID;
default = {};
type =
let
minecraftUUID =
lib.types.strMatching "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
// {
description = "Minecraft UUID";
};
in
lib.types.attrsOf minecraftUUID;
default = { };
description = ''
Whitelisted players, only has an effect when
{option}`services.minecraft-server.declarative` is
@ -124,8 +146,14 @@ in {
};
serverProperties = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ bool int str ]);
default = {};
type =
with lib.types;
attrsOf (oneOf [
bool
int
str
]);
default = { };
example = lib.literalExpression ''
{
server-port = 43000;
@ -155,7 +183,8 @@ in {
type = lib.types.separatedString " ";
default = "-Xmx2048M -Xms2048M";
# Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
example = "-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
example =
"-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+ "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+ "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
description = "JVM options for the Minecraft server.";
@ -166,13 +195,13 @@ in {
config = lib.mkIf cfg.enable {
users.users.minecraft = {
description = "Minecraft server service user";
home = cfg.dataDir;
createHome = true;
isSystemUser = true;
group = "minecraft";
description = "Minecraft server service user";
home = cfg.dataDir;
createHome = true;
isSystemUser = true;
group = "minecraft";
};
users.groups.minecraft = {};
users.groups.minecraft = { };
systemd.sockets.minecraft-server = {
bindsTo = [ "minecraft-server.service" ];
@ -187,10 +216,13 @@ in {
};
systemd.services.minecraft-server = {
description = "Minecraft Server Service";
wantedBy = [ "multi-user.target" ];
requires = [ "minecraft-server.socket" ];
after = [ "network.target" "minecraft-server.socket" ];
description = "Minecraft Server Service";
wantedBy = [ "multi-user.target" ];
requires = [ "minecraft-server.socket" ];
after = [
"network.target"
"minecraft-server.socket"
];
serviceConfig = {
ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
@ -218,7 +250,10 @@ in {
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
@ -226,49 +261,64 @@ in {
UMask = "0077";
};
preStart = ''
ln -sf ${eulaFile} eula.txt
'' + (if cfg.declarative then ''
preStart =
''
ln -sf ${eulaFile} eula.txt
''
+ (
if cfg.declarative then
''
if [ -e .declarative ]; then
if [ -e .declarative ]; then
# Was declarative before, no need to back up anything
ln -sf ${whitelistFile} whitelist.json
cp -f ${serverPropertiesFile} server.properties
# Was declarative before, no need to back up anything
ln -sf ${whitelistFile} whitelist.json
cp -f ${serverPropertiesFile} server.properties
else
else
# Declarative for the first time, backup stateful files
ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
# Declarative for the first time, backup stateful files
ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
# server.properties must have write permissions, because every time
# the server starts it first parses the file and then regenerates it..
chmod +w server.properties
echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
> .declarative
# server.properties must have write permissions, because every time
# the server starts it first parses the file and then regenerates it..
chmod +w server.properties
echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
> .declarative
fi
'' else ''
if [ -e .declarative ]; then
rm .declarative
fi
'');
fi
''
else
''
if [ -e .declarative ]; then
rm .declarative
fi
''
);
};
networking.firewall = lib.mkIf cfg.openFirewall (if cfg.declarative then {
allowedUDPPorts = [ serverPort ];
allowedTCPPorts = [ serverPort ]
++ lib.optional (queryPort != null) queryPort
++ lib.optional (rconPort != null) rconPort;
} else {
allowedUDPPorts = [ defaultServerPort ];
allowedTCPPorts = [ defaultServerPort ];
});
networking.firewall = lib.mkIf cfg.openFirewall (
if cfg.declarative then
{
allowedUDPPorts = [ serverPort ];
allowedTCPPorts =
[ serverPort ]
++ lib.optional (queryPort != null) queryPort
++ lib.optional (rconPort != null) rconPort;
}
else
{
allowedUDPPorts = [ defaultServerPort ];
allowedTCPPorts = [ defaultServerPort ];
}
);
assertions = [
{ assertion = cfg.eula;
message = "You must agree to Mojangs EULA to run minecraft-server."
{
assertion = cfg.eula;
message =
"You must agree to Mojangs EULA to run minecraft-server."
+ " Read https://account.mojang.com/documents/minecraft_eula and"
+ " set `services.minecraft-server.eula` to `true` if you agree.";
}

View file

@ -1,8 +1,14 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.amdgpu;
in {
in
{
options.hardware.amdgpu = {
legacySupport.enable = lib.mkEnableOption ''
using `amdgpu` kernel driver instead of `radeon` for Southern Islands

View file

@ -1,138 +1,191 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
{
imports = [
(lib.mkRenamedOptionModule
[ "virtualisation" "containers" "cdi" "dynamic" "nvidia" "enable" ]
[ "hardware" "nvidia-container-toolkit" "enable" ])
[ "hardware" "nvidia-container-toolkit" "enable" ]
)
];
options = let
mountType = {
options = {
hostPath = lib.mkOption {
type = lib.types.str;
description = "Host path.";
};
containerPath = lib.mkOption {
type = lib.types.str;
description = "Container path.";
};
mountOptions = lib.mkOption {
default = [ "ro" "nosuid" "nodev" "bind" ];
type = lib.types.listOf lib.types.str;
description = "Mount options.";
options =
let
mountType = {
options = {
hostPath = lib.mkOption {
type = lib.types.str;
description = "Host path.";
};
containerPath = lib.mkOption {
type = lib.types.str;
description = "Container path.";
};
mountOptions = lib.mkOption {
default = [
"ro"
"nosuid"
"nodev"
"bind"
];
type = lib.types.listOf lib.types.str;
description = "Mount options.";
};
};
};
in
{
hardware.nvidia-container-toolkit = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Enable dynamic CDI configuration for Nvidia devices by running
nvidia-container-toolkit on boot.
'';
};
mounts = lib.mkOption {
type = lib.types.listOf (lib.types.submodule mountType);
default = [ ];
description = "Mounts to be added to every container under the Nvidia CDI profile.";
};
mount-nvidia-executables = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount executables nvidia-smi, nvidia-cuda-mps-control, nvidia-cuda-mps-server,
nvidia-debugdump, nvidia-powerd and nvidia-ctk on containers.
'';
};
device-name-strategy = lib.mkOption {
default = "index";
type = lib.types.enum [
"index"
"uuid"
"type-index"
];
description = ''
Specify the strategy for generating device names,
passed to `nvidia-ctk cdi generate`. This will affect how
you reference the device using `nvidia.com/gpu=` in
the container runtime.
'';
};
mount-nvidia-docker-1-directories = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount nvidia-docker-1 directories on containers: /usr/local/nvidia/lib and
/usr/local/nvidia/lib64.
'';
};
package = lib.mkPackageOption pkgs "nvidia-container-toolkit" { };
};
};
in {
hardware.nvidia-container-toolkit = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Enable dynamic CDI configuration for Nvidia devices by running
nvidia-container-toolkit on boot.
'';
};
mounts = lib.mkOption {
type = lib.types.listOf (lib.types.submodule mountType);
default = [];
description = "Mounts to be added to every container under the Nvidia CDI profile.";
};
mount-nvidia-executables = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount executables nvidia-smi, nvidia-cuda-mps-control, nvidia-cuda-mps-server,
nvidia-debugdump, nvidia-powerd and nvidia-ctk on containers.
'';
};
device-name-strategy = lib.mkOption {
default = "index";
type = lib.types.enum [ "index" "uuid" "type-index" ];
description = ''
Specify the strategy for generating device names,
passed to `nvidia-ctk cdi generate`. This will affect how
you reference the device using `nvidia.com/gpu=` in
the container runtime.
'';
};
mount-nvidia-docker-1-directories = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount nvidia-docker-1 directories on containers: /usr/local/nvidia/lib and
/usr/local/nvidia/lib64.
'';
};
package = lib.mkPackageOption pkgs "nvidia-container-toolkit" { };
};
};
config = lib.mkIf config.hardware.nvidia-container-toolkit.enable {
assertions = [
{ assertion = config.hardware.nvidia.datacenter.enable || lib.elem "nvidia" config.services.xserver.videoDrivers;
{
assertion =
config.hardware.nvidia.datacenter.enable || lib.elem "nvidia" config.services.xserver.videoDrivers;
message = ''`nvidia-container-toolkit` requires nvidia datacenter or desktop drivers: set `hardware.nvidia.datacenter.enable` or add "nvidia" to `services.xserver.videoDrivers`'';
}];
}
];
virtualisation.docker = {
daemon.settings = lib.mkIf
(lib.versionAtLeast config.virtualisation.docker.package.version "25") {
features.cdi = true;
};
daemon.settings = lib.mkIf (lib.versionAtLeast config.virtualisation.docker.package.version "25") {
features.cdi = true;
};
rootless.daemon.settings = lib.mkIf
(config.virtualisation.docker.rootless.enable &&
(lib.versionAtLeast config.virtualisation.docker.package.version "25")) {
features.cdi = true;
};
rootless.daemon.settings =
lib.mkIf
(
config.virtualisation.docker.rootless.enable
&& (lib.versionAtLeast config.virtualisation.docker.package.version "25")
)
{
features.cdi = true;
};
};
hardware = {
graphics.enable = lib.mkIf (!config.hardware.nvidia.datacenter.enable) true;
nvidia-container-toolkit.mounts = let
nvidia-driver = config.hardware.nvidia.package;
in (lib.mkMerge [
[{ hostPath = pkgs.addDriverRunpath.driverLink;
containerPath = pkgs.addDriverRunpath.driverLink; }
{ hostPath = "${lib.getLib nvidia-driver}/etc";
containerPath = "${lib.getLib nvidia-driver}/etc"; }
{ hostPath = "${lib.getLib nvidia-driver}/share";
containerPath = "${lib.getLib nvidia-driver}/share"; }
{ hostPath = "${lib.getLib pkgs.glibc}/lib";
containerPath = "${lib.getLib pkgs.glibc}/lib"; }
{ hostPath = "${lib.getLib pkgs.glibc}/lib64";
containerPath = "${lib.getLib pkgs.glibc}/lib64"; }]
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-executables
[{ hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control";
containerPath = "/usr/bin/nvidia-cuda-mps-control"; }
{ hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server";
containerPath = "/usr/bin/nvidia-cuda-mps-server"; }
{ hostPath = lib.getExe' nvidia-driver "nvidia-debugdump";
containerPath = "/usr/bin/nvidia-debugdump"; }
{ hostPath = lib.getExe' nvidia-driver "nvidia-powerd";
containerPath = "/usr/bin/nvidia-powerd"; }
{ hostPath = lib.getExe' nvidia-driver "nvidia-smi";
containerPath = "/usr/bin/nvidia-smi"; }])
# nvidia-docker 1.0 uses /usr/local/nvidia/lib{,64}
# e.g.
# - https://gitlab.com/nvidia/container-images/cuda/-/blob/e3ff10eab3a1424fe394899df0e0f8ca5a410f0f/dist/12.3.1/ubi9/base/Dockerfile#L44
# - https://github.com/NVIDIA/nvidia-docker/blob/01d2c9436620d7dde4672e414698afe6da4a282f/src/nvidia/volumes.go#L104-L173
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-docker-1-directories
[{ hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib"; }
{ hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib64"; }])
]);
nvidia-container-toolkit.mounts =
let
nvidia-driver = config.hardware.nvidia.package;
in
(lib.mkMerge [
[
{
hostPath = pkgs.addDriverRunpath.driverLink;
containerPath = pkgs.addDriverRunpath.driverLink;
}
{
hostPath = "${lib.getLib nvidia-driver}/etc";
containerPath = "${lib.getLib nvidia-driver}/etc";
}
{
hostPath = "${lib.getLib nvidia-driver}/share";
containerPath = "${lib.getLib nvidia-driver}/share";
}
{
hostPath = "${lib.getLib pkgs.glibc}/lib";
containerPath = "${lib.getLib pkgs.glibc}/lib";
}
{
hostPath = "${lib.getLib pkgs.glibc}/lib64";
containerPath = "${lib.getLib pkgs.glibc}/lib64";
}
]
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-executables [
{
hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control";
containerPath = "/usr/bin/nvidia-cuda-mps-control";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server";
containerPath = "/usr/bin/nvidia-cuda-mps-server";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-debugdump";
containerPath = "/usr/bin/nvidia-debugdump";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-powerd";
containerPath = "/usr/bin/nvidia-powerd";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-smi";
containerPath = "/usr/bin/nvidia-smi";
}
])
# nvidia-docker 1.0 uses /usr/local/nvidia/lib{,64}
# e.g.
# - https://gitlab.com/nvidia/container-images/cuda/-/blob/e3ff10eab3a1424fe394899df0e0f8ca5a410f0f/dist/12.3.1/ubi9/base/Dockerfile#L44
# - https://github.com/NVIDIA/nvidia-docker/blob/01d2c9436620d7dde4672e414698afe6da4a282f/src/nvidia/volumes.go#L104-L173
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-docker-1-directories [
{
hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib";
}
{
hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib64";
}
])
]);
};
systemd.services.nvidia-container-toolkit-cdi-generator = {

View file

@ -28,15 +28,13 @@ let
/* eslint-disable no-unused-vars */
module.exports = {
afterConfig(config) {
${
builtins.concatStringsSep "" (
leafs (
lib.mapAttrsRecursive (path: val: ''
${builtins.concatStringsSep "." path} = ${builtins.toJSON val};
'') { config = settings; }
)
${builtins.concatStringsSep "" (
leafs (
lib.mapAttrsRecursive (path: val: ''
${builtins.concatStringsSep "." path} = ${builtins.toJSON val};
'') { config = settings; }
)
}
)}
${cfg.extraConfig}
},

View file

@ -1,15 +1,20 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.tlp;
enableRDW = config.networking.networkmanager.enable;
# TODO: Use this for having proper parameters in the future
mkTlpConfig = tlpConfig: lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault {
mkValueString = val:
if lib.isList val then "\"" + (toString val) + "\""
else toString val;
} "=";
} tlpConfig;
mkTlpConfig =
tlpConfig:
lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault {
mkValueString = val: if lib.isList val then "\"" + (toString val) + "\"" else toString val;
} "=";
} tlpConfig;
in
{
###### interface
@ -21,8 +26,17 @@ in
description = "Whether to enable the TLP power management daemon.";
};
settings = lib.mkOption {type = with lib.types; attrsOf (oneOf [bool int float str (listOf str)]);
default = {};
settings = lib.mkOption {
type =
with lib.types;
attrsOf (oneOf [
bool
int
float
str
(listOf str)
]);
default = { };
example = {
SATA_LINKPWR_ON_BAT = "med_power_with_dipm";
USB_BLACKLIST_PHONE = 1;
@ -58,35 +72,40 @@ in
Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead.
'';
assertions = [{
assertion = cfg.enable -> config.powerManagement.scsiLinkPolicy == null;
message = ''
`services.tlp.enable` and `config.powerManagement.scsiLinkPolicy` cannot be set both.
Set `services.tlp.settings.SATA_LINKPWR_ON_AC` and `services.tlp.settings.SATA_LINKPWR_ON_BAT` instead.
'';
}];
assertions = [
{
assertion = cfg.enable -> config.powerManagement.scsiLinkPolicy == null;
message = ''
`services.tlp.enable` and `config.powerManagement.scsiLinkPolicy` cannot be set both.
Set `services.tlp.settings.SATA_LINKPWR_ON_AC` and `services.tlp.settings.SATA_LINKPWR_ON_BAT` instead.
'';
}
];
environment.etc = {
"tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
} // lib.optionalAttrs enableRDW {
"NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
"${cfg.package}/lib/NetworkManager/dispatcher.d/99tlp-rdw-nm";
};
environment.etc =
{
"tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
}
// lib.optionalAttrs enableRDW {
"NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
"${cfg.package}/lib/NetworkManager/dispatcher.d/99tlp-rdw-nm";
};
environment.systemPackages = [ cfg.package ];
services.tlp.settings = let
cfg = config.powerManagement;
maybeDefault = val: lib.mkIf (val != null) (lib.mkDefault val);
in {
CPU_SCALING_GOVERNOR_ON_AC = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_GOVERNOR_ON_BAT = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_MIN_FREQ_ON_AC = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_AC = maybeDefault cfg.cpufreq.max;
CPU_SCALING_MIN_FREQ_ON_BAT = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_BAT = maybeDefault cfg.cpufreq.max;
};
services.tlp.settings =
let
cfg = config.powerManagement;
maybeDefault = val: lib.mkIf (val != null) (lib.mkDefault val);
in
{
CPU_SCALING_GOVERNOR_ON_AC = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_GOVERNOR_ON_BAT = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_MIN_FREQ_ON_AC = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_AC = maybeDefault cfg.cpufreq.max;
CPU_SCALING_MIN_FREQ_ON_BAT = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_BAT = maybeDefault cfg.cpufreq.max;
};
services.udev.packages = [ cfg.package ];

View file

@ -1,18 +1,22 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
udev = config.systemd.package;
cfg = config.services.udev;
initrdUdevRules = pkgs.runCommand "initrd-udev-rules" {} ''
initrdUdevRules = pkgs.runCommand "initrd-udev-rules" { } ''
mkdir -p $out/etc/udev/rules.d
for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do
ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d
done
'';
extraUdevRules = pkgs.writeTextFile {
name = "extra-udev-rules";
text = cfg.extraRules;
@ -36,143 +40,165 @@ let
'';
# Perform substitutions in all udev rules files.
udevRulesFor = { name, udevPackages, udevPath, udev, systemd, binPackages, initrdBin ? null }: pkgs.runCommand name
{ preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString udevPackages);
}
''
mkdir -p $out
shopt -s nullglob
set +o pipefail
udevRulesFor =
{
name,
udevPackages,
udevPath,
udev,
systemd,
binPackages,
initrdBin ? null,
}:
pkgs.runCommand name
{
preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString udevPackages);
}
''
mkdir -p $out
shopt -s nullglob
set +o pipefail
# Set a reasonable $PATH for programs called by udev rules.
echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
# Set a reasonable $PATH for programs called by udev rules.
echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
# Add the udev rules from other packages.
for i in $packages; do
echo "Adding rules for package $i"
for j in $i/{etc,lib}/udev/rules.d/*; do
echo "Copying $j to $out/$(basename $j)"
cat $j > $out/$(basename $j)
done
done
# Fix some paths in the standard udev rules. Hacky.
for i in $out/*.rules; do
substituteInPlace $i \
--replace-quiet \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
--replace-quiet \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
--replace-quiet \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \
--replace-quiet \"/bin/mount \"${pkgs.util-linux}/bin/mount \
--replace-quiet /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
--replace-quiet /usr/bin/cat ${pkgs.coreutils}/bin/cat \
--replace-quiet /usr/bin/basename ${pkgs.coreutils}/bin/basename 2>/dev/null
${lib.optionalString (initrdBin != null) ''
substituteInPlace $i --replace-quiet '/run/current-system/systemd' "${lib.removeSuffix "/bin" initrdBin}"
''}
done
echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
echo "FAIL"
echo "$i is called in udev rules but not installed by udev"
exit 1
fi
done
echo "OK"
echo -n "Checking that all programs called by absolute paths in udev rules exist... "
import_progs=$(grep 'IMPORT{program}="/' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
# if the path refers to /run/current-system/systemd, replace with config.systemd.package
if [[ $i == /run/current-system/systemd* ]]; then
i="${systemd}/''${i#/run/current-system/systemd/}"
fi
if [[ ! -x $i ]]; then
echo "FAIL"
echo "$i is called in udev rules but is not executable or does not exist"
exit 1
fi
done
echo "OK"
filesToFixup="$(for i in "$out"/*; do
# list all files referring to (/usr)/bin paths, but allow references to /bin/sh.
grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || :
done)"
if [ -n "$filesToFixup" ]; then
echo "Consider fixing the following udev rules:"
echo "$filesToFixup" | while read localFile; do
remoteFile="origin unknown"
for i in ${toString binPackages}; do
for j in "$i"/*/udev/rules.d/*; do
[ -e "$out/$(basename "$j")" ] || continue
[ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
remoteFile="originally from $j"
break 2
done
# Add the udev rules from other packages.
for i in $packages; do
echo "Adding rules for package $i"
for j in $i/{etc,lib}/udev/rules.d/*; do
echo "Copying $j to $out/$(basename $j)"
cat $j > $out/$(basename $j)
done
refs="$(
grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
| sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
)"
echo "$localFile ($remoteFile) contains references to $refs."
done
exit 1
fi
# If auto-configuration is disabled, then remove
# udev's 80-drivers.rules file, which contains rules for
# automatically calling modprobe.
${lib.optionalString (!config.boot.hardwareScan) ''
ln -s /dev/null $out/80-drivers.rules
''}
'';
hwdbBin = pkgs.runCommand "hwdb.bin"
{ preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString ([udev] ++ cfg.packages));
}
''
mkdir -p etc/udev/hwdb.d
for i in $packages; do
echo "Adding hwdb files for package $i"
for j in $i/{etc,lib}/udev/hwdb.d/*; do
ln -s $j etc/udev/hwdb.d/$(basename $j)
# Fix some paths in the standard udev rules. Hacky.
for i in $out/*.rules; do
substituteInPlace $i \
--replace-quiet \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
--replace-quiet \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
--replace-quiet \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \
--replace-quiet \"/bin/mount \"${pkgs.util-linux}/bin/mount \
--replace-quiet /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
--replace-quiet /usr/bin/cat ${pkgs.coreutils}/bin/cat \
--replace-quiet /usr/bin/basename ${pkgs.coreutils}/bin/basename 2>/dev/null
${lib.optionalString (initrdBin != null) ''
substituteInPlace $i --replace-quiet '/run/current-system/systemd' "${lib.removeSuffix "/bin" initrdBin}"
''}
done
done
echo "Generating hwdb database..."
# hwdb --update doesn't return error code even on errors!
res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
echo "$res"
[ -z "$(echo "$res" | egrep '^Error')" ]
mv etc/udev/hwdb.bin $out
'';
echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
echo "FAIL"
echo "$i is called in udev rules but not installed by udev"
exit 1
fi
done
echo "OK"
compressFirmware = firmware:
if config.hardware.firmwareCompression == "none" || (firmware.compressFirmware or true) == false then firmware
else if config.hardware.firmwareCompression == "zstd" then pkgs.compressFirmwareZstd firmware
else pkgs.compressFirmwareXz firmware;
echo -n "Checking that all programs called by absolute paths in udev rules exist... "
import_progs=$(grep 'IMPORT{program}="/' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
# if the path refers to /run/current-system/systemd, replace with config.systemd.package
if [[ $i == /run/current-system/systemd* ]]; then
i="${systemd}/''${i#/run/current-system/systemd/}"
fi
if [[ ! -x $i ]]; then
echo "FAIL"
echo "$i is called in udev rules but is not executable or does not exist"
exit 1
fi
done
echo "OK"
filesToFixup="$(for i in "$out"/*; do
# list all files referring to (/usr)/bin paths, but allow references to /bin/sh.
grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || :
done)"
if [ -n "$filesToFixup" ]; then
echo "Consider fixing the following udev rules:"
echo "$filesToFixup" | while read localFile; do
remoteFile="origin unknown"
for i in ${toString binPackages}; do
for j in "$i"/*/udev/rules.d/*; do
[ -e "$out/$(basename "$j")" ] || continue
[ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
remoteFile="originally from $j"
break 2
done
done
refs="$(
grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
| sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
)"
echo "$localFile ($remoteFile) contains references to $refs."
done
exit 1
fi
# If auto-configuration is disabled, then remove
# udev's 80-drivers.rules file, which contains rules for
# automatically calling modprobe.
${lib.optionalString (!config.boot.hardwareScan) ''
ln -s /dev/null $out/80-drivers.rules
''}
'';
hwdbBin =
pkgs.runCommand "hwdb.bin"
{
preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString ([ udev ] ++ cfg.packages));
}
''
mkdir -p etc/udev/hwdb.d
for i in $packages; do
echo "Adding hwdb files for package $i"
for j in $i/{etc,lib}/udev/hwdb.d/*; do
ln -s $j etc/udev/hwdb.d/$(basename $j)
done
done
echo "Generating hwdb database..."
# hwdb --update doesn't return error code even on errors!
res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
echo "$res"
[ -z "$(echo "$res" | egrep '^Error')" ]
mv etc/udev/hwdb.bin $out
'';
compressFirmware =
firmware:
if
config.hardware.firmwareCompression == "none" || (firmware.compressFirmware or true) == false
then
firmware
else if config.hardware.firmwareCompression == "zstd" then
pkgs.compressFirmwareZstd firmware
else
pkgs.compressFirmwareXz firmware;
# Udev has a 512-character limit for ENV{PATH}, so create a symlink
# tree to work around this.
udevPath = pkgs.buildEnv {
name = "udev-path";
paths = cfg.path;
pathsToLink = [ "/bin" "/sbin" ];
pathsToLink = [
"/bin"
"/sbin"
];
ignoreCollisions = true;
};
@ -201,7 +227,7 @@ in
packages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [];
default = [ ];
description = ''
List of packages containing {command}`udev` rules.
All files found in
@ -214,7 +240,7 @@ in
path = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [];
default = [ ];
description = ''
Packages added to the {env}`PATH` environment variable when
executing programs from Udev rules.
@ -256,7 +282,7 @@ in
hardware.firmware = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [];
default = [ ];
description = ''
List of packages containing firmware files. Such files
will be loaded automatically if the kernel asks for them
@ -266,19 +292,29 @@ in
precedence. Note that you must rebuild your system if you add
files to any of these directories.
'';
apply = list: pkgs.buildEnv {
name = "firmware";
paths = map compressFirmware list;
pathsToLink = [ "/lib/firmware" ];
ignoreCollisions = true;
};
apply =
list:
pkgs.buildEnv {
name = "firmware";
paths = map compressFirmware list;
pathsToLink = [ "/lib/firmware" ];
ignoreCollisions = true;
};
};
hardware.firmwareCompression = lib.mkOption {
type = lib.types.enum [ "xz" "zstd" "none" ];
default = if config.boot.kernelPackages.kernelAtLeast "5.19" then "zstd"
else if config.boot.kernelPackages.kernelAtLeast "5.3" then "xz"
else "none";
type = lib.types.enum [
"xz"
"zstd"
"none"
];
default =
if config.boot.kernelPackages.kernelAtLeast "5.19" then
"zstd"
else if config.boot.kernelPackages.kernelAtLeast "5.3" then
"xz"
else
"none";
defaultText = "auto";
description = ''
Whether to compress firmware files.
@ -309,7 +345,7 @@ in
packages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [];
default = [ ];
description = ''
*This will only be used when systemd is used in stage 1.*
@ -323,7 +359,7 @@ in
binPackages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [];
default = [ ];
description = ''
*This will only be used when systemd is used in stage 1.*
@ -351,21 +387,22 @@ in
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.hardware.firmwareCompression == "zstd" -> config.boot.kernelPackages.kernelAtLeast "5.19";
assertion =
config.hardware.firmwareCompression == "zstd" -> config.boot.kernelPackages.kernelAtLeast "5.19";
message = ''
The firmware compression method is set to zstd, but the kernel version is too old.
The kernel version must be at least 5.3 to use zstd compression.
'';
}
{
assertion = config.hardware.firmwareCompression == "xz" -> config.boot.kernelPackages.kernelAtLeast "5.3";
assertion =
config.hardware.firmwareCompression == "xz" -> config.boot.kernelPackages.kernelAtLeast "5.3";
message = ''
The firmware compression method is set to xz, but the kernel version is too old.
The kernel version must be at least 5.3 to use xz compression.
@ -375,18 +412,28 @@ in
services.udev.extraRules = nixosRules;
services.udev.packages = [ extraUdevRules extraHwdbFile ];
services.udev.packages = [
extraUdevRules
extraHwdbFile
];
services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.util-linux udev ];
services.udev.path = [
pkgs.coreutils
pkgs.gnused
pkgs.gnugrep
pkgs.util-linux
udev
];
boot.kernelParams = lib.mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
''
cat <<'EOF' > $out/99-local.rules
${config.boot.initrd.services.udev.rules}
EOF
'';
boot.initrd.extraUdevRulesCommands =
lib.mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
''
cat <<'EOF' > $out/99-local.rules
${config.boot.initrd.services.udev.rules}
EOF
'';
boot.initrd.services.udev.rules = nixosInitrdRules;
@ -415,32 +462,39 @@ in
udevPath = config.boot.initrd.systemd.contents."/bin".source;
udev = config.boot.initrd.systemd.package;
systemd = config.boot.initrd.systemd.package;
binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ];
binPackages = config.boot.initrd.services.udev.binPackages ++ [
config.boot.initrd.systemd.contents."/bin".source
];
};
};
# Insert initrd rules
boot.initrd.services.udev.packages = [
initrdUdevRules
(lib.mkIf (config.boot.initrd.services.udev.rules != "") (pkgs.writeTextFile {
name = "initrd-udev-rules";
destination = "/etc/udev/rules.d/99-local.rules";
text = config.boot.initrd.services.udev.rules;
}))
(lib.mkIf (config.boot.initrd.services.udev.rules != "") (
pkgs.writeTextFile {
name = "initrd-udev-rules";
destination = "/etc/udev/rules.d/99-local.rules";
text = config.boot.initrd.services.udev.rules;
}
))
];
environment.etc = {
"udev/rules.d".source = udevRulesFor {
name = "udev-rules";
udevPackages = cfg.packages;
systemd = config.systemd.package;
binPackages = cfg.packages;
inherit udevPath udev;
environment.etc =
{
"udev/rules.d".source = udevRulesFor {
name = "udev-rules";
udevPackages = cfg.packages;
systemd = config.systemd.package;
binPackages = cfg.packages;
inherit udevPath udev;
};
"udev/hwdb.bin".source = hwdbBin;
}
// lib.optionalAttrs config.boot.modprobeConfig.enable {
# We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat.
"modprobe.d/firmware.conf".text =
"options firmware_class path=${config.hardware.firmware}/lib/firmware";
};
"udev/hwdb.bin".source = hwdbBin;
} // lib.optionalAttrs config.boot.modprobeConfig.enable {
# We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat.
"modprobe.d/firmware.conf".text = "options firmware_class path=${config.hardware.firmware}/lib/firmware";
};
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "UNIX")
@ -468,6 +522,9 @@ in
};
imports = [
(lib.mkRenamedOptionModule [ "services" "udev" "initrdRules" ] [ "boot" "initrd" "services" "udev" "rules" ])
(lib.mkRenamedOptionModule
[ "services" "udev" "initrdRules" ]
[ "boot" "initrd" "services" "udev" "rules" ]
)
];
}

View file

@ -1,5 +1,10 @@
# Upower daemon.
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.upower;
@ -242,7 +247,7 @@ in
systemd.packages = [ cfg.package ];
environment.etc."UPower/UPower.conf".text = lib.generators.toINI {} {
environment.etc."UPower/UPower.conf".text = lib.generators.toINI { } {
UPower = {
EnableWattsUpPro = cfg.enableWattsUpPro;
NoPollBatteries = cfg.noPollBatteries;

File diff suppressed because it is too large Load diff

View file

@ -1,48 +1,89 @@
{ config, lib, pkgs, utils, ... }:
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.logrotate;
generateLine = n: v:
if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
else if builtins.elem n [ "frequency" ] then "${v}\n"
else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
then "${n}\n ${v}\n endscript\n"
else if lib.isInt v then "${n} ${toString v}\n"
else if v == true then "${n}\n"
else if v == false then "no${n}\n"
else "${n} ${v}\n";
generateSection = indent: settings: lib.concatStringsSep (lib.fixedWidthString indent " " "") (
lib.filter (x: x != null) (lib.mapAttrsToList generateLine settings)
);
generateLine =
n: v:
if
builtins.elem n [
"files"
"priority"
"enable"
"global"
]
|| v == null
then
null
else if builtins.elem n [ "frequency" ] then
"${v}\n"
else if
builtins.elem n [
"firstaction"
"lastaction"
"prerotate"
"postrotate"
"preremove"
]
then
"${n}\n ${v}\n endscript\n"
else if lib.isInt v then
"${n} ${toString v}\n"
else if v == true then
"${n}\n"
else if v == false then
"no${n}\n"
else
"${n} ${v}\n";
generateSection =
indent: settings:
lib.concatStringsSep (lib.fixedWidthString indent " " "") (
lib.filter (x: x != null) (lib.mapAttrsToList generateLine settings)
);
# generateSection includes a final newline hence weird closing brace
mkConf = settings:
if settings.global or false then generateSection 0 settings
else ''
${lib.concatMapStringsSep "\n" (files: ''"${files}"'') (lib.toList settings.files)} {
${generateSection 2 settings}}
'';
mkConf =
settings:
if settings.global or false then
generateSection 0 settings
else
''
${lib.concatMapStringsSep "\n" (files: ''"${files}"'') (lib.toList settings.files)} {
${generateSection 2 settings}}
'';
settings = lib.sortProperties (lib.attrValues (lib.filterAttrs (_: settings: settings.enable) (
lib.foldAttrs lib.recursiveUpdate { } [
{
header = {
enable = true;
missingok = true;
notifempty = true;
frequency = "weekly";
rotate = 4;
};
}
cfg.settings
{ header = { global = true; priority = 100; }; }
]
)));
settings = lib.sortProperties (
lib.attrValues (
lib.filterAttrs (_: settings: settings.enable) (
lib.foldAttrs lib.recursiveUpdate { } [
{
header = {
enable = true;
missingok = true;
notifempty = true;
frequency = "weekly";
rotate = 4;
};
}
cfg.settings
{
header = {
global = true;
priority = 100;
};
}
]
)
)
);
configFile = pkgs.writeTextFile {
name = "logrotate.conf";
text = lib.concatStringsSep "\n" (
map mkConf settings
);
text = lib.concatStringsSep "\n" (map mkConf settings);
checkPhase = lib.optionalString cfg.checkConfig ''
# logrotate --debug also checks that users specified in config
# file exist, but we only have sandboxed users here so brown these
@ -79,15 +120,27 @@ let
'';
};
mailOption =
lib.optionalString (lib.foldr (n: a: a || (n.mail or false) != false) false (lib.attrValues cfg.settings))
"--mail=${pkgs.mailutils}/bin/mail";
mailOption = lib.optionalString (lib.foldr (n: a: a || (n.mail or false) != false) false (
lib.attrValues cfg.settings
)) "--mail=${pkgs.mailutils}/bin/mail";
in
{
imports = [
(lib.mkRemovedOptionModule [ "services" "logrotate" "config" ] "Modify services.logrotate.settings.header instead")
(lib.mkRemovedOptionModule [ "services" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.header instead")
(lib.mkRemovedOptionModule [ "services" "logrotate" "paths" ] "Add attributes to services.logrotate.settings instead")
(lib.mkRemovedOptionModule [
"services"
"logrotate"
"config"
] "Modify services.logrotate.settings.header instead")
(lib.mkRemovedOptionModule [
"services"
"logrotate"
"extraConfig"
] "Modify services.logrotate.settings.header instead")
(lib.mkRemovedOptionModule [
"services"
"logrotate"
"paths"
] "Add attributes to services.logrotate.settings instead")
];
options = {
@ -136,57 +189,70 @@ in
''';
};
};
'';
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
freeformType = with lib.types; attrsOf (nullOr (oneOf [ int bool str ]));
'';
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
freeformType =
with lib.types;
attrsOf (
nullOr (oneOf [
int
bool
str
])
);
options = {
enable = lib.mkEnableOption "setting individual kill switch" // {
default = true;
};
options = {
enable = lib.mkEnableOption "setting individual kill switch" // {
default = true;
};
global = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether this setting is a global option or not: set to have these
settings apply to all files settings with a higher priority.
'';
};
files = lib.mkOption {
type = with lib.types; either str (listOf str);
default = name;
defaultText = ''
The attrset name if not specified
'';
description = ''
Single or list of files for which rules are defined.
The files are quoted with double-quotes in logrotate configuration,
so globs and spaces are supported.
Note this setting is ignored if globals is true.
'';
};
global = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether this setting is a global option or not: set to have these
settings apply to all files settings with a higher priority.
'';
};
files = lib.mkOption {
type = with lib.types; either str (listOf str);
default = name;
defaultText = ''
The attrset name if not specified
'';
description = ''
Single or list of files for which rules are defined.
The files are quoted with double-quotes in logrotate configuration,
so globs and spaces are supported.
Note this setting is ignored if globals is true.
'';
};
frequency = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
How often to rotate the logs. Defaults to previously set global setting,
which itself defaults to weekly.
'';
};
frequency = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
How often to rotate the logs. Defaults to previously set global setting,
which itself defaults to weekly.
'';
};
priority = lib.mkOption {
type = lib.types.int;
default = 1000;
description = ''
Order of this logrotate block in relation to the others. The semantics are
the same as with `lib.mkOrder`. Smaller values are inserted first.
'';
};
};
priority = lib.mkOption {
type = lib.types.int;
default = 1000;
description = ''
Order of this logrotate block in relation to the others. The semantics are
the same as with `lib.mkOrder`. Smaller values are inserted first.
'';
};
};
}));
}
)
);
};
configFile = lib.mkOption {
@ -233,7 +299,7 @@ in
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
default = [ ];
description = "Additional command line arguments to pass on logrotate invocation";
};
};
@ -248,54 +314,56 @@ in
];
startAt = "hourly";
serviceConfig = {
Type = "oneshot";
ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
serviceConfig =
{
Type = "oneshot";
ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
# performance
Nice = 19;
IOSchedulingClass = "best-effort";
IOSchedulingPriority = 7;
# performance
Nice = 19;
IOSchedulingClass = "best-effort";
IOSchedulingPriority = 7;
# hardening
CapabilityBoundingSet = [
"CAP_CHOWN"
"CAP_DAC_OVERRIDE"
"CAP_FOWNER"
"CAP_KILL"
"CAP_SETUID"
"CAP_SETGID"
];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "full";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = false; # can create sgid directories
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged @resources"
"@chown @setuid"
];
UMask = "0027";
} // lib.optionalAttrs (!cfg.allowNetworking) {
PrivateNetwork = true; # e.g. mail delivery
RestrictAddressFamilies = [ "AF_UNIX" ];
};
# hardening
CapabilityBoundingSet = [
"CAP_CHOWN"
"CAP_DAC_OVERRIDE"
"CAP_FOWNER"
"CAP_KILL"
"CAP_SETUID"
"CAP_SETGID"
];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "full";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = false; # can create sgid directories
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged @resources"
"@chown @setuid"
];
UMask = "0027";
}
// lib.optionalAttrs (!cfg.allowNetworking) {
PrivateNetwork = true; # e.g. mail delivery
RestrictAddressFamilies = [ "AF_UNIX" ];
};
};
systemd.services.logrotate-checkconf = {
description = "Logrotate configuration check";

View file

@ -1,23 +1,32 @@
{ config, lib, pkgs, ... }: with lib;
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.promtail;
format = pkgs.formats.json {};
prettyJSON = conf: with lib; pipe conf [
(flip removeAttrs [ "_module" ])
(format.generate "promtail-config.json")
];
format = pkgs.formats.json { };
prettyJSON =
conf:
with lib;
pipe conf [
(flip removeAttrs [ "_module" ])
(format.generate "promtail-config.json")
];
allowSystemdJournal = cfg.configuration ? scrape_configs && lib.any (v: v ? journal) cfg.configuration.scrape_configs;
allowSystemdJournal =
cfg.configuration ? scrape_configs && lib.any (v: v ? journal) cfg.configuration.scrape_configs;
allowPositionsFile = !lib.hasPrefix "/var/cache/promtail" positionsFile;
positionsFile = cfg.configuration.positions.filename;
configFile = if cfg.configFile != null
then cfg.configFile
else prettyJSON cfg.configuration;
configFile = if cfg.configFile != null then cfg.configFile else prettyJSON cfg.configuration;
in {
in
{
options.services.promtail = with types; {
enable = mkEnableOption "the Promtail ingresser";
@ -40,7 +49,7 @@ in {
extraFlags = mkOption {
type = listOf str;
default = [];
default = [ ];
example = [ "--server.http-listen-port=3101" ];
description = ''
Specify a list of additional command line flags,
@ -61,47 +70,50 @@ in {
${lib.getExe pkgs.promtail} -config.file=${configFile} -check-syntax
'';
serviceConfig = {
Restart = "on-failure";
TimeoutStopSec = 10;
serviceConfig =
{
Restart = "on-failure";
TimeoutStopSec = 10;
ExecStart = "${pkgs.promtail}/bin/promtail -config.file=${configFile} ${escapeShellArgs cfg.extraFlags}";
ExecStart = "${pkgs.promtail}/bin/promtail -config.file=${configFile} ${escapeShellArgs cfg.extraFlags}";
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
RestrictSUIDSGID = true;
PrivateMounts = true;
CacheDirectory = "promtail";
ReadWritePaths = lib.optional allowPositionsFile (builtins.dirOf positionsFile);
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
RestrictSUIDSGID = true;
PrivateMounts = true;
CacheDirectory = "promtail";
ReadWritePaths = lib.optional allowPositionsFile (builtins.dirOf positionsFile);
User = "promtail";
Group = "promtail";
User = "promtail";
Group = "promtail";
CapabilityBoundingSet = "";
NoNewPrivileges = true;
CapabilityBoundingSet = "";
NoNewPrivileges = true;
ProtectKernelModules = true;
SystemCallArchitectures = "native";
ProtectKernelLogs = true;
ProtectClock = true;
ProtectKernelModules = true;
SystemCallArchitectures = "native";
ProtectKernelLogs = true;
ProtectClock = true;
LockPersonality = true;
ProtectHostname = true;
RestrictRealtime = true;
MemoryDenyWriteExecute = true;
PrivateUsers = true;
LockPersonality = true;
ProtectHostname = true;
RestrictRealtime = true;
MemoryDenyWriteExecute = true;
PrivateUsers = true;
SupplementaryGroups = lib.optional (allowSystemdJournal) "systemd-journal";
} // (optionalAttrs (!pkgs.stdenv.hostPlatform.isAarch64) { # FIXME: figure out why this breaks on aarch64
SystemCallFilter = "@system-service";
});
SupplementaryGroups = lib.optional (allowSystemdJournal) "systemd-journal";
}
// (optionalAttrs (!pkgs.stdenv.hostPlatform.isAarch64) {
# FIXME: figure out why this breaks on aarch64
SystemCallFilter = "@system-service";
});
};
users.groups.promtail = {};
users.groups.promtail = { };
users.users.promtail = {
description = "Promtail service user";
isSystemUser = true;

View file

@ -103,5 +103,5 @@ in
};
};
meta.maintainers = with lib.maintainers; [RTUnreal];
meta.maintainers = with lib.maintainers; [ RTUnreal ];
}

View file

@ -1,40 +1,57 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.mailman;
inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; withLDAP = cfg.ldap.enable; })
mailmanEnv webEnv;
inherit
(pkgs.mailmanPackages.buildEnvs {
withHyperkitty = cfg.hyperkitty.enable;
withLDAP = cfg.ldap.enable;
})
mailmanEnv
webEnv
;
withPostgresql = config.services.postgresql.enable;
# This deliberately doesn't use recursiveUpdate so users can
# override the defaults.
webSettings = {
DEFAULT_FROM_EMAIL = cfg.siteOwner;
SERVER_EMAIL = cfg.siteOwner;
ALLOWED_HOSTS = [ "localhost" "127.0.0.1" ] ++ cfg.webHosts;
COMPRESS_OFFLINE = true;
STATIC_ROOT = "/var/lib/mailman-web-static";
MEDIA_ROOT = "/var/lib/mailman-web/media";
LOGGING = {
version = 1;
disable_existing_loggers = true;
handlers.console.class = "logging.StreamHandler";
loggers.django = {
handlers = [ "console" ];
level = "INFO";
webSettings =
{
DEFAULT_FROM_EMAIL = cfg.siteOwner;
SERVER_EMAIL = cfg.siteOwner;
ALLOWED_HOSTS = [
"localhost"
"127.0.0.1"
] ++ cfg.webHosts;
COMPRESS_OFFLINE = true;
STATIC_ROOT = "/var/lib/mailman-web-static";
MEDIA_ROOT = "/var/lib/mailman-web/media";
LOGGING = {
version = 1;
disable_existing_loggers = true;
handlers.console.class = "logging.StreamHandler";
loggers.django = {
handlers = [ "console" ];
level = "INFO";
};
};
};
HAYSTACK_CONNECTIONS.default = {
ENGINE = "haystack.backends.whoosh_backend.WhooshEngine";
PATH = "/var/lib/mailman-web/fulltext-index";
};
} // lib.optionalAttrs cfg.enablePostfix {
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend";
EMAIL_HOST = "127.0.0.1";
EMAIL_PORT = 25;
} // cfg.webSettings;
HAYSTACK_CONNECTIONS.default = {
ENGINE = "haystack.backends.whoosh_backend.WhooshEngine";
PATH = "/var/lib/mailman-web/fulltext-index";
};
}
// lib.optionalAttrs cfg.enablePostfix {
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend";
EMAIL_HOST = "127.0.0.1";
EMAIL_PORT = 25;
}
// cfg.webSettings;
webSettingsJSON = pkgs.writeText "settings.json" (builtins.toJSON webSettings);
@ -45,9 +62,11 @@ let
transport_file_type: hash
'';
mailmanCfg = lib.generators.toINI {} (lib.recursiveUpdate cfg.settings {
webservice.admin_pass = "#NIXOS_MAILMAN_REST_API_PASS_SECRET#";
});
mailmanCfg = lib.generators.toINI { } (
lib.recursiveUpdate cfg.settings {
webservice.admin_pass = "#NIXOS_MAILMAN_REST_API_PASS_SECRET#";
}
);
mailmanCfgFile = pkgs.writeText "mailman-raw.cfg" mailmanCfg;
@ -64,13 +83,16 @@ let
api_key: @API_KEY@
'';
in {
in
{
###### interface
imports = [
(lib.mkRenamedOptionModule [ "services" "mailman" "hyperkittyBaseUrl" ]
[ "services" "mailman" "hyperkitty" "baseUrl" ])
(lib.mkRenamedOptionModule
[ "services" "mailman" "hyperkittyBaseUrl" ]
[ "services" "mailman" "hyperkitty" "baseUrl" ]
)
(lib.mkRemovedOptionModule [ "services" "mailman" "hyperkittyApiKey" ] ''
The Hyperkitty API key is now generated on first run, and not
@ -146,9 +168,17 @@ in {
groupSearch = {
type = lib.mkOption {
type = lib.types.enum [
"posixGroup" "groupOfNames" "memberDNGroup" "nestedMemberDNGroup" "nestedGroupOfNames"
"groupOfUniqueNames" "nestedGroupOfUniqueNames" "activeDirectoryGroup" "nestedActiveDirectoryGroup"
"organizationalRoleGroup" "nestedOrganizationalRoleGroup"
"posixGroup"
"groupOfNames"
"memberDNGroup"
"nestedMemberDNGroup"
"nestedGroupOfNames"
"groupOfUniqueNames"
"nestedGroupOfUniqueNames"
"activeDirectoryGroup"
"nestedActiveDirectoryGroup"
"organizationalRoleGroup"
"nestedOrganizationalRoleGroup"
];
default = "posixGroup";
apply = v: "${lib.toUpper (lib.substring 0 1 v)}${lib.substring 1 (lib.stringLength v) v}Type";
@ -229,7 +259,7 @@ in {
webHosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
default = [ ];
description = ''
The list of hostnames and/or IP addresses from which the Mailman Web
UI will accept requests. By default, "localhost" and "127.0.0.1" are
@ -249,7 +279,7 @@ in {
webSettings = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
description = ''
Overrides for the default mailman-web Django settings.
'';
@ -268,8 +298,10 @@ in {
uwsgiSettings = lib.mkOption {
default = { };
example = { uwsgi.buffer-size = 8192; };
inherit (pkgs.formats.json {}) type;
example = {
uwsgi.buffer-size = 8192;
};
inherit (pkgs.formats.json { }) type;
description = ''
Extra configuration to merge into uwsgi config.
'';
@ -288,7 +320,7 @@ in {
settings = lib.mkOption {
description = "Settings for mailman.cfg";
type = lib.types.attrsOf (lib.types.attrsOf lib.types.str);
default = {};
default = { };
};
hyperkitty = {
@ -311,72 +343,105 @@ in {
config = lib.mkIf cfg.enable {
services.mailman.settings = {
mailman.site_owner = lib.mkDefault cfg.siteOwner;
mailman.layout = "fhs";
services.mailman.settings =
{
mailman.site_owner = lib.mkDefault cfg.siteOwner;
mailman.layout = "fhs";
"paths.fhs" = {
bin_dir = "${pkgs.mailmanPackages.mailman}/bin";
var_dir = "/var/lib/mailman";
queue_dir = "$var_dir/queue";
template_dir = "$var_dir/templates";
log_dir = "/var/log/mailman";
lock_dir = "/run/mailman/lock";
etc_dir = "/etc";
pid_file = "/run/mailman/master.pid";
};
"paths.fhs" = {
bin_dir = "${pkgs.mailmanPackages.mailman}/bin";
var_dir = "/var/lib/mailman";
queue_dir = "$var_dir/queue";
template_dir = "$var_dir/templates";
log_dir = "/var/log/mailman";
lock_dir = "/run/mailman/lock";
etc_dir = "/etc";
pid_file = "/run/mailman/master.pid";
};
mta.configuration = lib.mkDefault (if cfg.enablePostfix then "${postfixMtaConfig}" else throw "When Mailman Postfix integration is disabled, set `services.mailman.settings.mta.configuration` to the path of the config file required to integrate with your MTA.");
mta.configuration = lib.mkDefault (
if cfg.enablePostfix then
"${postfixMtaConfig}"
else
throw "When Mailman Postfix integration is disabled, set `services.mailman.settings.mta.configuration` to the path of the config file required to integrate with your MTA."
);
"archiver.hyperkitty" = lib.mkIf cfg.hyperkitty.enable {
class = "mailman_hyperkitty.Archiver";
enable = "yes";
configuration = "/var/lib/mailman/mailman-hyperkitty.cfg";
};
} // (let
loggerNames = ["root" "archiver" "bounce" "config" "database" "debug" "error" "fromusenet" "http" "locks" "mischief" "plugins" "runner" "smtp"];
loggerSectionNames = map (n: "logging.${n}") loggerNames;
in lib.genAttrs loggerSectionNames(name: { handler = "stderr"; })
);
assertions = let
inherit (config.services) postfix;
requirePostfixHash = optionPath: dataFile:
"archiver.hyperkitty" = lib.mkIf cfg.hyperkitty.enable {
class = "mailman_hyperkitty.Archiver";
enable = "yes";
configuration = "/var/lib/mailman/mailman-hyperkitty.cfg";
};
}
// (
let
expected = "hash:/var/lib/mailman/data/${dataFile}";
value = lib.attrByPath optionPath [] postfix;
loggerNames = [
"root"
"archiver"
"bounce"
"config"
"database"
"debug"
"error"
"fromusenet"
"http"
"locks"
"mischief"
"plugins"
"runner"
"smtp"
];
loggerSectionNames = map (n: "logging.${n}") loggerNames;
in
{ assertion = postfix.enable -> lib.isList value && lib.elem expected value;
lib.genAttrs loggerSectionNames (name: {
handler = "stderr";
})
);
assertions =
let
inherit (config.services) postfix;
requirePostfixHash =
optionPath: dataFile:
let
expected = "hash:/var/lib/mailman/data/${dataFile}";
value = lib.attrByPath optionPath [ ] postfix;
in
{
assertion = postfix.enable -> lib.isList value && lib.elem expected value;
message = ''
services.postfix.${lib.concatStringsSep "." optionPath} must contain
"${expected}".
See <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>.
'';
};
in [
{ assertion = cfg.webHosts != [];
message = ''
services.mailman.serve.enable requires there to be at least one entry
in services.mailman.webHosts.
'';
}
] ++ (lib.optionals cfg.enablePostfix [
{ assertion = postfix.enable;
message = ''
Mailman's default NixOS configuration requires Postfix to be enabled.
in
[
{
assertion = cfg.webHosts != [ ];
message = ''
services.mailman.serve.enable requires there to be at least one entry
in services.mailman.webHosts.
'';
}
]
++ (lib.optionals cfg.enablePostfix [
{
assertion = postfix.enable;
message = ''
Mailman's default NixOS configuration requires Postfix to be enabled.
If you want to use another MTA, set services.mailman.enablePostfix
to false and configure settings in services.mailman.settings.mta.
If you want to use another MTA, set services.mailman.enablePostfix
to false and configure settings in services.mailman.settings.mta.
Refer to <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>
for more info.
'';
}
(requirePostfixHash [ "config" "relay_domains" ] "postfix_domains")
(requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp")
(requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
]);
Refer to <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>
for more info.
'';
}
(requirePostfixHash [ "config" "relay_domains" ] "postfix_domains")
(requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp")
(requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
]);
users.users.mailman = {
description = "GNU Mailman";
@ -388,7 +453,7 @@ in {
isSystemUser = true;
group = "mailman";
};
users.groups.mailman = {};
users.groups.mailman = { };
environment.etc."mailman3/settings.py".text = ''
import os
@ -427,9 +492,13 @@ in {
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("${cfg.ldap.groupSearch.ou}",
ldap.SCOPE_SUBTREE, "${cfg.ldap.groupSearch.query}")
AUTH_LDAP_USER_ATTR_MAP = {
${lib.concatStrings (lib.flip lib.mapAttrsToList cfg.ldap.attrMap (key: value: ''
"${key}": "${value}",
''))}
${lib.concatStrings (
lib.flip lib.mapAttrsToList cfg.ldap.attrMap (
key: value: ''
"${key}": "${value}",
''
)
)}
}
${lib.optionalString (cfg.ldap.superUserGroup != null) ''
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
@ -443,7 +512,7 @@ in {
''}
'';
services.nginx = lib.mkIf (cfg.serve.enable && cfg.webHosts != []) {
services.nginx = lib.mkIf (cfg.serve.enable && cfg.webHosts != [ ]) {
enable = lib.mkDefault true;
virtualHosts = lib.genAttrs cfg.webHosts (webHost: {
locations = {
@ -454,215 +523,263 @@ in {
proxyTimeout = lib.mkDefault "120s";
};
environment.systemPackages = [ (pkgs.buildEnv {
name = "mailman-tools";
# We don't want to pollute the system PATH with a python
# interpreter etc. so let's pick only the stuff we actually
# want from {web,mailman}Env
pathsToLink = ["/bin"];
paths = [ mailmanEnv webEnv ];
# Only mailman-related stuff is installed, the rest is removed
# in `postBuild`.
ignoreCollisions = true;
postBuild = ''
find $out/bin/ -mindepth 1 -not -name "mailman*" -delete
'' + lib.optionalString config.security.sudo.enable ''
mv $out/bin/mailman $out/bin/.mailman-wrapped
echo '#!${pkgs.runtimeShell}
sudo=exec
if [[ "$USER" != mailman ]]; then
sudo="exec /run/wrappers/bin/sudo -u mailman"
fi
$sudo ${placeholder "out"}/bin/.mailman-wrapped "$@"
' > $out/bin/mailman
chmod +x $out/bin/mailman
'';
}) ];
environment.systemPackages = [
(pkgs.buildEnv {
name = "mailman-tools";
# We don't want to pollute the system PATH with a python
# interpreter etc. so let's pick only the stuff we actually
# want from {web,mailman}Env
pathsToLink = [ "/bin" ];
paths = [
mailmanEnv
webEnv
];
# Only mailman-related stuff is installed, the rest is removed
# in `postBuild`.
ignoreCollisions = true;
postBuild =
''
find $out/bin/ -mindepth 1 -not -name "mailman*" -delete
''
+ lib.optionalString config.security.sudo.enable ''
mv $out/bin/mailman $out/bin/.mailman-wrapped
echo '#!${pkgs.runtimeShell}
sudo=exec
if [[ "$USER" != mailman ]]; then
sudo="exec /run/wrappers/bin/sudo -u mailman"
fi
$sudo ${placeholder "out"}/bin/.mailman-wrapped "$@"
' > $out/bin/mailman
chmod +x $out/bin/mailman
'';
})
];
services.postfix = lib.mkIf cfg.enablePostfix {
recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP
recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP
config = {
owner_request_special = "no"; # Mailman handles -owner addresses on its own
owner_request_special = "no"; # Mailman handles -owner addresses on its own
};
};
systemd.sockets.mailman-uwsgi = lib.mkIf cfg.serve.enable {
wantedBy = ["sockets.target"];
before = ["nginx.service"];
wantedBy = [ "sockets.target" ];
before = [ "nginx.service" ];
socketConfig.ListenStream = "/run/mailman-web.socket";
};
systemd.services = {
mailman = {
description = "GNU Mailman Master Process";
before = lib.optional cfg.enablePostfix "postfix.service";
after = [ "network.target" ]
++ lib.optional cfg.enablePostfix "postfix-setup.service"
++ lib.optional withPostgresql "postgresql.service";
restartTriggers = [ mailmanCfgFile ];
requires = lib.optional withPostgresql "postgresql.service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${mailmanEnv}/bin/mailman start";
ExecStop = "${mailmanEnv}/bin/mailman stop";
User = "mailman";
Group = "mailman";
Type = "forking";
RuntimeDirectory = "mailman";
LogsDirectory = "mailman";
PIDFile = "/run/mailman/master.pid";
Restart = "on-failure";
TimeoutStartSec = 180;
TimeoutStopSec = 180;
systemd.services =
{
mailman = {
description = "GNU Mailman Master Process";
before = lib.optional cfg.enablePostfix "postfix.service";
after =
[ "network.target" ]
++ lib.optional cfg.enablePostfix "postfix-setup.service"
++ lib.optional withPostgresql "postgresql.service";
restartTriggers = [ mailmanCfgFile ];
requires = lib.optional withPostgresql "postgresql.service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${mailmanEnv}/bin/mailman start";
ExecStop = "${mailmanEnv}/bin/mailman stop";
User = "mailman";
Group = "mailman";
Type = "forking";
RuntimeDirectory = "mailman";
LogsDirectory = "mailman";
PIDFile = "/run/mailman/master.pid";
Restart = "on-failure";
TimeoutStartSec = 180;
TimeoutStopSec = 180;
};
};
};
mailman-settings = {
description = "Generate settings files (including secrets) for Mailman";
before = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
requiredBy = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
path = with pkgs; [ jq ];
after = lib.optional withPostgresql "postgresql.service";
requires = lib.optional withPostgresql "postgresql.service";
serviceConfig.RemainAfterExit = true;
serviceConfig.Type = "oneshot";
script = ''
install -m0750 -o mailman -g mailman ${mailmanCfgFile} /etc/mailman.cfg
${if cfg.restApiPassFile == null then ''
sed -i "s/#NIXOS_MAILMAN_REST_API_PASS_SECRET#/$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)/g" \
/etc/mailman.cfg
'' else ''
${pkgs.replace-secret}/bin/replace-secret \
'#NIXOS_MAILMAN_REST_API_PASS_SECRET#' \
${cfg.restApiPassFile} \
/etc/mailman.cfg
''}
mailman-settings = {
description = "Generate settings files (including secrets) for Mailman";
before = [
"mailman.service"
"mailman-web-setup.service"
"mailman-uwsgi.service"
"hyperkitty.service"
];
requiredBy = [
"mailman.service"
"mailman-web-setup.service"
"mailman-uwsgi.service"
"hyperkitty.service"
];
path = with pkgs; [ jq ];
after = lib.optional withPostgresql "postgresql.service";
requires = lib.optional withPostgresql "postgresql.service";
serviceConfig.RemainAfterExit = true;
serviceConfig.Type = "oneshot";
script = ''
install -m0750 -o mailman -g mailman ${mailmanCfgFile} /etc/mailman.cfg
${
if cfg.restApiPassFile == null then
''
sed -i "s/#NIXOS_MAILMAN_REST_API_PASS_SECRET#/$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)/g" \
/etc/mailman.cfg
''
else
''
${pkgs.replace-secret}/bin/replace-secret \
'#NIXOS_MAILMAN_REST_API_PASS_SECRET#' \
${cfg.restApiPassFile} \
/etc/mailman.cfg
''
}
mailmanDir=/var/lib/mailman
mailmanWebDir=/var/lib/mailman-web
mailmanDir=/var/lib/mailman
mailmanWebDir=/var/lib/mailman-web
mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
mailmanWebCfg=$mailmanWebDir/settings_local.json
mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
mailmanWebCfg=$mailmanWebDir/settings_local.json
install -m 0775 -o mailman -g mailman -d /var/lib/mailman-web-static
install -m 0770 -o mailman -g mailman -d $mailmanDir
install -m 0770 -o ${cfg.webUser} -g mailman -d $mailmanWebDir
install -m 0775 -o mailman -g mailman -d /var/lib/mailman-web-static
install -m 0770 -o mailman -g mailman -d $mailmanDir
install -m 0770 -o ${cfg.webUser} -g mailman -d $mailmanWebDir
if [ ! -e $mailmanWebCfg ]; then
hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
if [ ! -e $mailmanWebCfg ]; then
hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
install -m 0440 -o root -g mailman \
<(jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
--arg archiver_key "$hyperkittyApiKey" \
--arg secret_key "$secretKey") \
"$mailmanWebCfg"
fi
install -m 0440 -o root -g mailman \
<(jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
--arg archiver_key "$hyperkittyApiKey" \
--arg secret_key "$secretKey") \
"$mailmanWebCfg"
fi
hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")"
mailmanCfgTmp=$(mktemp)
sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
chown mailman:mailman "$mailmanCfgTmp"
mv "$mailmanCfgTmp" "$mailmanCfg"
'';
};
mailman-web-setup = {
description = "Prepare mailman-web files and database";
before = [ "mailman-uwsgi.service" ];
requiredBy = [ "mailman-uwsgi.service" ];
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
script = ''
[[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete
${webEnv}/bin/mailman-web migrate
${webEnv}/bin/mailman-web collectstatic
${webEnv}/bin/mailman-web compress
'';
serviceConfig = {
User = cfg.webUser;
Group = "mailman";
Type = "oneshot";
WorkingDirectory = "/var/lib/mailman-web";
hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")"
mailmanCfgTmp=$(mktemp)
sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
chown mailman:mailman "$mailmanCfgTmp"
mv "$mailmanCfgTmp" "$mailmanCfg"
'';
};
};
mailman-uwsgi = lib.mkIf cfg.serve.enable (let
uwsgiConfig = lib.recursiveUpdate {
uwsgi = {
type = "normal";
plugins = ["python3"];
home = webEnv;
http = "127.0.0.1:18507";
buffer-size = 8192;
mailman-web-setup = {
description = "Prepare mailman-web files and database";
before = [ "mailman-uwsgi.service" ];
requiredBy = [ "mailman-uwsgi.service" ];
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
script = ''
[[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete
${webEnv}/bin/mailman-web migrate
${webEnv}/bin/mailman-web collectstatic
${webEnv}/bin/mailman-web compress
'';
serviceConfig = {
User = cfg.webUser;
Group = "mailman";
Type = "oneshot";
WorkingDirectory = "/var/lib/mailman-web";
};
};
mailman-uwsgi = lib.mkIf cfg.serve.enable (
let
uwsgiConfig = lib.recursiveUpdate {
uwsgi =
{
type = "normal";
plugins = [ "python3" ];
home = webEnv;
http = "127.0.0.1:18507";
buffer-size = 8192;
}
// (
if cfg.serve.virtualRoot == "/" then
{ module = "mailman_web.wsgi:application"; }
else
{
mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application";
manage-script-name = true;
}
);
} cfg.serve.uwsgiSettings;
uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
in
{
wantedBy = [ "multi-user.target" ];
after = lib.optional withPostgresql "postgresql.service";
requires = [
"mailman-uwsgi.socket"
"mailman-web-setup.service"
] ++ lib.optional withPostgresql "postgresql.service";
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
serviceConfig = {
# Since the mailman-web settings.py obstinately creates a logs
# dir in the cwd, change to the (writable) runtime directory before
# starting uwsgi.
ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${
pkgs.uwsgi.override {
plugins = [ "python3" ];
python3 = webEnv.python;
}
}/bin/uwsgi --json ${uwsgiConfigFile}";
User = cfg.webUser;
Group = "mailman";
RuntimeDirectory = "mailman-uwsgi";
Restart = "on-failure";
};
}
// (if cfg.serve.virtualRoot == "/"
then { module = "mailman_web.wsgi:application"; }
else {
mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application";
manage-script-name = true;
});
} cfg.serve.uwsgiSettings;
uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
in {
wantedBy = ["multi-user.target"];
after = lib.optional withPostgresql "postgresql.service";
requires = ["mailman-uwsgi.socket" "mailman-web-setup.service"]
++ lib.optional withPostgresql "postgresql.service";
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
serviceConfig = {
# Since the mailman-web settings.py obstinately creates a logs
# dir in the cwd, change to the (writable) runtime directory before
# starting uwsgi.
ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; python3 = webEnv.python; }}/bin/uwsgi --json ${uwsgiConfigFile}";
User = cfg.webUser;
Group = "mailman";
RuntimeDirectory = "mailman-uwsgi";
Restart = "on-failure";
};
});
);
mailman-daily = {
description = "Trigger daily Mailman events";
startAt = "daily";
restartTriggers = [ mailmanCfgFile ];
serviceConfig = {
ExecStart = "${mailmanEnv}/bin/mailman digests --send";
User = "mailman";
Group = "mailman";
mailman-daily = {
description = "Trigger daily Mailman events";
startAt = "daily";
restartTriggers = [ mailmanCfgFile ];
serviceConfig = {
ExecStart = "${mailmanEnv}/bin/mailman digests --send";
User = "mailman";
Group = "mailman";
};
};
};
hyperkitty = lib.mkIf cfg.hyperkitty.enable {
description = "GNU Hyperkitty QCluster Process";
after = [ "network.target" ];
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
wantedBy = [ "mailman.service" "multi-user.target" ];
serviceConfig = {
ExecStart = "${webEnv}/bin/mailman-web qcluster";
User = cfg.webUser;
Group = "mailman";
WorkingDirectory = "/var/lib/mailman-web";
Restart = "on-failure";
hyperkitty = lib.mkIf cfg.hyperkitty.enable {
description = "GNU Hyperkitty QCluster Process";
after = [ "network.target" ];
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
wantedBy = [
"mailman.service"
"multi-user.target"
];
serviceConfig = {
ExecStart = "${webEnv}/bin/mailman-web qcluster";
User = cfg.webUser;
Group = "mailman";
WorkingDirectory = "/var/lib/mailman-web";
Restart = "on-failure";
};
};
};
} // lib.flip lib.mapAttrs' {
"minutely" = "minutely";
"quarter_hourly" = "*:00/15";
"hourly" = "hourly";
"daily" = "daily";
"weekly" = "weekly";
"yearly" = "yearly";
} (name: startAt:
lib.nameValuePair "hyperkitty-${name}" (lib.mkIf cfg.hyperkitty.enable {
description = "Trigger ${name} Hyperkitty events";
inherit startAt;
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
serviceConfig = {
ExecStart = "${webEnv}/bin/mailman-web runjobs ${name}";
User = cfg.webUser;
Group = "mailman";
WorkingDirectory = "/var/lib/mailman-web";
};
}));
}
// lib.flip lib.mapAttrs'
{
"minutely" = "minutely";
"quarter_hourly" = "*:00/15";
"hourly" = "hourly";
"daily" = "daily";
"weekly" = "weekly";
"yearly" = "yearly";
}
(
name: startAt:
lib.nameValuePair "hyperkitty-${name}" (
lib.mkIf cfg.hyperkitty.enable {
description = "Trigger ${name} Hyperkitty events";
inherit startAt;
restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
serviceConfig = {
ExecStart = "${webEnv}/bin/mailman-web runjobs ${name}";
User = cfg.webUser;
Group = "mailman";
WorkingDirectory = "/var/lib/mailman-web";
};
}
)
);
};
meta = {

View file

@ -1,79 +1,86 @@
{ lib
, config
, pkgs
, ...
{
lib,
config,
pkgs,
...
}:
let
inherit (lib) mkEnableOption mkOption mkIf types;
inherit (lib)
mkEnableOption
mkOption
mkIf
types
;
format = pkgs.formats.toml { };
cfg = config.services.hebbot;
settingsFile = format.generate "config.toml" cfg.settings;
mkTemplateOption = templateName: mkOption {
type = types.path;
description = ''
A path to the Markdown file for the ${templateName}.
'';
};
in
{
meta.maintainers = [ lib.maintainers.raitobezarius ];
options.services.hebbot = {
enable = mkEnableOption "hebbot";
package = lib.mkPackageOption pkgs "hebbot" {};
botPasswordFile = mkOption {
type = types.path;
description = ''
A path to the password file for your bot.
Consider using a path that does not end up in your Nix store
as it would be world readable.
'';
};
templates = {
project = mkTemplateOption "project template";
report = mkTemplateOption "report template";
section = mkTemplateOption "section template";
};
settings = mkOption {
type = format.type;
default = { };
description = ''
Configuration for Hebbot, see, for examples:
- <https://github.com/matrix-org/twim-config/blob/master/config.toml>
- <https://gitlab.gnome.org/Teams/Websites/thisweek.gnome.org/-/blob/main/hebbot/config.toml>
'';
};
mkTemplateOption =
templateName:
mkOption {
type = types.path;
description = ''
A path to the Markdown file for the ${templateName}.
'';
};
in
{
meta.maintainers = [ lib.maintainers.raitobezarius ];
options.services.hebbot = {
enable = mkEnableOption "hebbot";
package = lib.mkPackageOption pkgs "hebbot" { };
botPasswordFile = mkOption {
type = types.path;
description = ''
A path to the password file for your bot.
config = mkIf cfg.enable {
systemd.services.hebbot = {
description = "hebbot - a TWIM-style Matrix bot written in Rust";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
Consider using a path that does not end up in your Nix store
as it would be world readable.
'';
};
templates = {
project = mkTemplateOption "project template";
report = mkTemplateOption "report template";
section = mkTemplateOption "section template";
};
settings = mkOption {
type = format.type;
default = { };
description = ''
Configuration for Hebbot, see, for examples:
preStart = ''
ln -sf ${cfg.templates.project} ./project_template.md
ln -sf ${cfg.templates.report} ./report_template.md
ln -sf ${cfg.templates.section} ./section_template.md
ln -sf ${settingsFile} ./config.toml
'';
- <https://github.com/matrix-org/twim-config/blob/master/config.toml>
- <https://gitlab.gnome.org/Teams/Websites/thisweek.gnome.org/-/blob/main/hebbot/config.toml>
'';
};
};
script = ''
export BOT_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/bot-password-file)"
${lib.getExe cfg.package}
'';
config = mkIf cfg.enable {
systemd.services.hebbot = {
description = "hebbot - a TWIM-style Matrix bot written in Rust";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
Restart = "on-failure";
LoadCredential = "bot-password-file:${cfg.botPasswordFile}";
RestartSec = "10s";
StateDirectory = "hebbot";
WorkingDirectory = "/var/lib/hebbot";
preStart = ''
ln -sf ${cfg.templates.project} ./project_template.md
ln -sf ${cfg.templates.report} ./report_template.md
ln -sf ${cfg.templates.section} ./section_template.md
ln -sf ${settingsFile} ./config.toml
'';
script = ''
export BOT_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/bot-password-file)"
${lib.getExe cfg.package}
'';
serviceConfig = {
DynamicUser = true;
Restart = "on-failure";
LoadCredential = "bot-password-file:${cfg.botPasswordFile}";
RestartSec = "10s";
StateDirectory = "hebbot";
WorkingDirectory = "/var/lib/hebbot";
};
};
};
}

View file

@ -3,13 +3,14 @@
config,
pkgs,
...
}: let
}:
let
cfg = config.services.mautrix-whatsapp;
dataDir = "/var/lib/mautrix-whatsapp";
registrationFile = "${dataDir}/whatsapp-registration.yaml";
settingsFile = "${dataDir}/config.json";
settingsFileUnsubstituted = settingsFormat.generate "mautrix-whatsapp-config-unsubstituted.json" cfg.settings;
settingsFormat = pkgs.formats.json {};
settingsFormat = pkgs.formats.json { };
appservicePort = 29318;
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
@ -29,8 +30,8 @@
bridge = {
username_template = "whatsapp_{{.}}";
displayname_template = "{{if .BusinessName}}{{.BusinessName}}{{else if .PushName}}{{.PushName}}{{else}}{{.JID}}{{end}} (WA)";
double_puppet_server_map = {};
login_shared_secret_map = {};
double_puppet_server_map = { };
login_shared_secret_map = { };
command_prefix = "!wa";
permissions."*" = "relay";
relay.enabled = true;
@ -45,7 +46,8 @@
};
};
in {
in
{
options.services.mautrix-whatsapp = {
enable = lib.mkEnableOption "mautrix-whatsapp, a puppeting/relaybot bridge between Matrix and WhatsApp";
@ -129,7 +131,7 @@ in {
description = "Mautrix-WhatsApp bridge user";
};
users.groups.mautrix-whatsapp = {};
users.groups.mautrix-whatsapp = { };
services.matrix-synapse = lib.mkIf cfg.registerToSynapse {
settings.app_service_config_files = [ registrationFile ];
@ -138,18 +140,20 @@ in {
serviceConfig.SupplementaryGroups = [ "mautrix-whatsapp" ];
};
services.mautrix-whatsapp.settings = lib.mkMerge (map mkDefaults [
defaultConfig
# Note: this is defined here to avoid the docs depending on `config`
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
]);
services.mautrix-whatsapp.settings = lib.mkMerge (
map mkDefaults [
defaultConfig
# Note: this is defined here to avoid the docs depending on `config`
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
]
);
systemd.services.mautrix-whatsapp = {
description = "Mautrix-WhatsApp Service - A WhatsApp bridge for Matrix";
wantedBy = ["multi-user.target"];
wants = ["network-online.target"] ++ cfg.serviceDependencies;
after = ["network-online.target"] ++ cfg.serviceDependencies;
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
preStart = ''
# substitute the settings file by environment variables
@ -216,12 +220,12 @@ in {
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
SystemCallFilter = ["@system-service"];
SystemCallFilter = [ "@system-service" ];
Type = "simple";
UMask = 0027;
UMask = 27;
};
restartTriggers = [settingsFileUnsubstituted];
restartTriggers = [ settingsFileUnsubstituted ];
};
};
meta.maintainers = with lib.maintainers; [frederictobiasc];
meta.maintainers = with lib.maintainers; [ frederictobiasc ];
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.mjolnir;
@ -23,8 +28,15 @@ let
};
moduleConfigFile = pkgs.writeText "module-config.yaml" (
lib.generators.toYAML { } (lib.filterAttrs (_: v: v != null)
(lib.fold lib.recursiveUpdate { } [ yamlConfig cfg.settings ])));
lib.generators.toYAML { } (
lib.filterAttrs (_: v: v != null) (
lib.fold lib.recursiveUpdate { } [
yamlConfig
cfg.settings
]
)
)
);
# these config files will be merged one after the other to build the final config
configFiles = [
@ -36,7 +48,9 @@ let
# replace all secret strings using replace-secret
generateConfig = pkgs.writeShellScript "mjolnir-generate-config" (
let
yqEvalStr = lib.concatImapStringsSep " * " (pos: _: "select(fileIndex == ${toString (pos - 1)})") configFiles;
yqEvalStr = lib.concatImapStringsSep " * " (
pos: _: "select(fileIndex == ${toString (pos - 1)})"
) configFiles;
yqEvalArgs = lib.concatStringsSep " " configFiles;
in
''
@ -190,15 +204,20 @@ in
# which breaks older configs using pantalaimon or access tokens
services.mjolnir.settings.encryption.use = lib.mkDefault false;
services.pantalaimon-headless.instances."mjolnir" = lib.mkIf cfg.pantalaimon.enable
{
services.pantalaimon-headless.instances."mjolnir" =
lib.mkIf cfg.pantalaimon.enable {
homeserver = cfg.homeserverUrl;
} // cfg.pantalaimon.options;
}
// cfg.pantalaimon.options;
systemd.services.mjolnir = {
description = "mjolnir - a moderation tool for Matrix";
wants = [ "network-online.target" ] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
after = [ "network-online.target" ] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
wants = [
"network-online.target"
] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
after = [
"network-online.target"
] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
@ -215,15 +234,16 @@ in
User = "mjolnir";
Restart = "on-failure";
/* TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME"
DynamicUser = true;
LoadCredential = [] ++
lib.optionals (cfg.accessTokenFile != null) [
"access_token:${cfg.accessTokenFile}"
] ++
lib.optionals (cfg.pantalaimon.passwordFile != null) [
"pantalaimon_password:${cfg.pantalaimon.passwordFile}"
];
/*
TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME"
DynamicUser = true;
LoadCredential = [] ++
lib.optionals (cfg.accessTokenFile != null) [
"access_token:${cfg.accessTokenFile}"
] ++
lib.optionals (cfg.pantalaimon.passwordFile != null) [
"pantalaimon_password:${cfg.pantalaimon.passwordFile}"
];
*/
};
};

View file

@ -4,44 +4,39 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.services.anki-sync-server;
name = "anki-sync-server";
specEscape = replaceStrings ["%"] ["%%"];
usersWithIndexes =
lists.imap1 (i: user: {
i = i;
user = user;
})
cfg.users;
specEscape = replaceStrings [ "%" ] [ "%%" ];
usersWithIndexes = lists.imap1 (i: user: {
i = i;
user = user;
}) cfg.users;
usersWithIndexesFile = filter (x: x.user.passwordFile != null) usersWithIndexes;
usersWithIndexesNoFile = filter (x: x.user.passwordFile == null && x.user.password != null) usersWithIndexes;
usersWithIndexesNoFile = filter (
x: x.user.passwordFile == null && x.user.password != null
) usersWithIndexes;
anki-sync-server-run = pkgs.writeShellScript "anki-sync-server-run" ''
# When services.anki-sync-server.users.passwordFile is set,
# each password file is passed as a systemd credential, which is mounted in
# a file system exposed to the service. Here we read the passwords from
# the credential files to pass them as environment variables to the Anki
# sync server.
${
concatMapStringsSep
"\n"
(x: ''
read -r pass < "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username}
export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"$pass"
'')
usersWithIndexesFile
}
${concatMapStringsSep "\n" (x: ''
read -r pass < "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username}
export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"$pass"
'') usersWithIndexesFile}
# For users where services.anki-sync-server.users.password isn't set,
# export passwords in environment variables in plaintext.
${
concatMapStringsSep
"\n"
(x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}'')
usersWithIndexesNoFile
}
${concatMapStringsSep "\n" (
x:
''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}''
) usersWithIndexesNoFile}
exec ${lib.getExe cfg.package}
'';
in {
in
{
options.services.anki-sync-server = {
enable = mkEnableOption "anki-sync-server";
@ -68,7 +63,6 @@ in {
description = "Base directory where user(s) synchronized data will be stored.";
};
openFirewall = mkOption {
default = false;
type = types.bool;
@ -76,7 +70,8 @@ in {
};
users = mkOption {
type = with types;
type =
with types;
listOf (submodule {
options = {
username = mkOption {
@ -116,13 +111,13 @@ in {
message = "At least one username-password pair must be set.";
}
];
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
systemd.services.anki-sync-server = {
description = "anki-sync-server: Anki sync server built into Anki";
after = ["network.target"];
wantedBy = ["multi-user.target"];
path = [cfg.package];
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ cfg.package ];
environment = {
SYNC_BASE = cfg.baseDirectory;
SYNC_HOST = specEscape cfg.address;
@ -135,16 +130,15 @@ in {
StateDirectory = name;
ExecStart = anki-sync-server-run;
Restart = "always";
LoadCredential =
map
(x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}")
usersWithIndexesFile;
LoadCredential = map (
x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}"
) usersWithIndexesFile;
};
};
};
meta = {
maintainers = with maintainers; [telotortium];
maintainers = with maintainers; [ telotortium ];
doc = ./anki-sync-server.md;
};
}

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.bazarr;
in
@ -72,7 +77,7 @@ in
};
users.groups = lib.mkIf (cfg.group == "bazarr") {
bazarr = {};
bazarr = { };
};
};
}

View file

@ -1,9 +1,23 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.beesd;
logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; };
logLevels = {
emerg = 0;
alert = 1;
crit = 2;
err = 3;
warning = 4;
notice = 5;
info = 6;
debug = 7;
};
fsOptions = with lib.types; {
options.spec = lib.mkOption {
@ -84,8 +98,9 @@ in
};
};
config = {
systemd.services = lib.mapAttrs'
(name: fs: lib.nameValuePair "beesd@${name}" {
systemd.services = lib.mapAttrs' (
name: fs:
lib.nameValuePair "beesd@${name}" {
description = "Block-level BTRFS deduplication for %i";
after = [ "sysinit.target" ];
@ -120,7 +135,7 @@ in
};
unitConfig.RequiresMountsFor = lib.mkIf (lib.hasPrefix "/" fs.spec) fs.spec;
wantedBy = [ "multi-user.target" ];
})
cfg.filesystems;
}
) cfg.filesystems;
};
}

View file

@ -1,10 +1,16 @@
{ lib, pkgs, config, ... }:
{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.domoticz;
pkgDesc = "Domoticz home automation";
in {
in
{
options = {

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.dwm-status;
@ -26,7 +31,16 @@ in
};
order = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
type = lib.types.listOf (
lib.types.enum [
"audio"
"backlight"
"battery"
"cpu_load"
"network"
"time"
]
);
description = ''
List of enabled features in order.
'';
@ -44,7 +58,6 @@ in
};
###### implementation
config = lib.mkIf cfg.enable {

View file

@ -1,4 +1,10 @@
{ config, lib, options, pkgs, ... }:
{
config,
lib,
options,
pkgs,
...
}:
with lib;
@ -17,7 +23,7 @@ let
RUN_MODE = prod
WORK_PATH = ${cfg.stateDir}
${generators.toINI {} cfg.settings}
${generators.toINI { } cfg.settings}
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
'';
@ -25,22 +31,56 @@ in
{
imports = [
(mkRenamedOptionModule [ "services" "gitea" "cookieSecure" ] [ "services" "gitea" "settings" "session" "COOKIE_SECURE" ])
(mkRenamedOptionModule [ "services" "gitea" "disableRegistration" ] [ "services" "gitea" "settings" "service" "DISABLE_REGISTRATION" ])
(mkRenamedOptionModule [ "services" "gitea" "domain" ] [ "services" "gitea" "settings" "server" "DOMAIN" ])
(mkRenamedOptionModule [ "services" "gitea" "httpAddress" ] [ "services" "gitea" "settings" "server" "HTTP_ADDR" ])
(mkRenamedOptionModule [ "services" "gitea" "httpPort" ] [ "services" "gitea" "settings" "server" "HTTP_PORT" ])
(mkRenamedOptionModule [ "services" "gitea" "log" "level" ] [ "services" "gitea" "settings" "log" "LEVEL" ])
(mkRenamedOptionModule [ "services" "gitea" "log" "rootPath" ] [ "services" "gitea" "settings" "log" "ROOT_PATH" ])
(mkRenamedOptionModule [ "services" "gitea" "rootUrl" ] [ "services" "gitea" "settings" "server" "ROOT_URL" ])
(mkRenamedOptionModule [ "services" "gitea" "ssh" "clonePort" ] [ "services" "gitea" "settings" "server" "SSH_PORT" ])
(mkRenamedOptionModule [ "services" "gitea" "staticRootPath" ] [ "services" "gitea" "settings" "server" "STATIC_ROOT_PATH" ])
(mkRenamedOptionModule
[ "services" "gitea" "cookieSecure" ]
[ "services" "gitea" "settings" "session" "COOKIE_SECURE" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "disableRegistration" ]
[ "services" "gitea" "settings" "service" "DISABLE_REGISTRATION" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "domain" ]
[ "services" "gitea" "settings" "server" "DOMAIN" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "httpAddress" ]
[ "services" "gitea" "settings" "server" "HTTP_ADDR" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "httpPort" ]
[ "services" "gitea" "settings" "server" "HTTP_PORT" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "log" "level" ]
[ "services" "gitea" "settings" "log" "LEVEL" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "log" "rootPath" ]
[ "services" "gitea" "settings" "log" "ROOT_PATH" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "rootUrl" ]
[ "services" "gitea" "settings" "server" "ROOT_URL" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "ssh" "clonePort" ]
[ "services" "gitea" "settings" "server" "SSH_PORT" ]
)
(mkRenamedOptionModule
[ "services" "gitea" "staticRootPath" ]
[ "services" "gitea" "settings" "server" "STATIC_ROOT_PATH" ]
)
(mkChangedOptionModule [ "services" "gitea" "enableUnixSocket" ] [ "services" "gitea" "settings" "server" "PROTOCOL" ] (
config: if config.services.gitea.enableUnixSocket then "http+unix" else "http"
))
(mkChangedOptionModule
[ "services" "gitea" "enableUnixSocket" ]
[ "services" "gitea" "settings" "server" "PROTOCOL" ]
(config: if config.services.gitea.enableUnixSocket then "http+unix" else "http")
)
(mkRemovedOptionModule [ "services" "gitea" "ssh" "enable" ] "services.gitea.ssh.enable has been migrated into freeform setting services.gitea.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
(mkRemovedOptionModule [ "services" "gitea" "ssh" "enable" ]
"services.gitea.ssh.enable has been migrated into freeform setting services.gitea.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted"
)
];
options = {
@ -86,7 +126,11 @@ in
database = {
type = mkOption {
type = types.enum [ "sqlite3" "mysql" "postgres" ];
type = types.enum [
"sqlite3"
"mysql"
"postgres"
];
example = "mysql";
default = "sqlite3";
description = "Database engine to use.";
@ -143,7 +187,13 @@ in
socket = mkOption {
type = types.nullOr types.path;
default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
default =
if (cfg.database.createDatabase && usePostgresql) then
"/run/postgresql"
else if (cfg.database.createDatabase && useMysql) then
"/run/mysqld/mysqld.sock"
else
null;
defaultText = literalExpression "null";
example = "/run/mysqld/mysqld.sock";
description = "Path to the unix socket file to use for authentication.";
@ -194,7 +244,13 @@ in
};
type = mkOption {
type = types.enum [ "image" "recaptcha" "hcaptcha" "mcaptcha" "cfturnstile" ];
type = types.enum [
"image"
"recaptcha"
"hcaptcha"
"mcaptcha"
"cfturnstile"
];
default = "image";
example = "recaptcha";
description = "The type of CAPTCHA to use for Gitea.";
@ -245,7 +301,18 @@ in
};
type = mkOption {
type = types.enum [ "zip" "rar" "tar" "sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ];
type = types.enum [
"zip"
"rar"
"tar"
"sz"
"tar.gz"
"tar.xz"
"tar.bz2"
"tar.br"
"tar.lz4"
"tar.zst"
];
default = "zip";
description = "Archive format used to store the dump file.";
};
@ -308,7 +375,7 @@ in
};
settings = mkOption {
default = {};
default = { };
description = ''
Gitea configuration. Refer to <https://docs.gitea.io/en-us/config-cheat-sheet/>
for details on supported values.
@ -343,21 +410,35 @@ in
};
LEVEL = mkOption {
default = "Info";
type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
type = types.enum [
"Trace"
"Debug"
"Info"
"Warn"
"Error"
"Critical"
];
description = "General log level.";
};
};
server = {
PROTOCOL = mkOption {
type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
type = types.enum [
"http"
"https"
"fcgi"
"http+unix"
"fcgi+unix"
];
default = "http";
description = ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
};
HTTP_ADDR = mkOption {
type = types.either types.str types.path;
default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0";
default =
if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0";
defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0"'';
description = "Listen address. Must be a path when using a unix socket.";
};
@ -445,10 +526,12 @@ in
config = mkIf cfg.enable {
assertions = [
{ assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
{
assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
}
{ assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
{
assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
message = ''
When creating a database via NixOS, the db user and db name must be equal!
If you already have an existing DB+user and this assertion is new, you can safely set
@ -457,115 +540,133 @@ in
'';
}
{
assertion = cfg.captcha.enable -> cfg.captcha.type != "image" -> (cfg.captcha.secretFile != null && cfg.captcha.siteKey != null);
assertion =
cfg.captcha.enable
-> cfg.captcha.type != "image"
-> (cfg.captcha.secretFile != null && cfg.captcha.siteKey != null);
message = ''
Using a CAPTCHA service that is not `image` requires providing a CAPTCHA secret through
the `captcha.secretFile` option and a CAPTCHA site key through the `captcha.siteKey` option.
'';
}
{
assertion = cfg.captcha.url != null -> (builtins.elem cfg.captcha.type ["mcaptcha" "recaptcha"]);
assertion =
cfg.captcha.url != null
-> (builtins.elem cfg.captcha.type [
"mcaptcha"
"recaptcha"
]);
message = ''
`captcha.url` is only relevant when `captcha.type` is `mcaptcha` or `recaptcha`.
'';
}
];
services.gitea.settings = let
captchaPrefix = optionalString cfg.captcha.enable ({
image = "IMAGE";
recaptcha = "RECAPTCHA";
hcaptcha = "HCAPTCHA";
mcaptcha = "MCAPTCHA";
cfturnstile = "CF_TURNSTILE";
}."${cfg.captcha.type}");
in {
"cron.update_checker".ENABLED = lib.mkDefault false;
services.gitea.settings =
let
captchaPrefix = optionalString cfg.captcha.enable (
{
image = "IMAGE";
recaptcha = "RECAPTCHA";
hcaptcha = "HCAPTCHA";
mcaptcha = "MCAPTCHA";
cfturnstile = "CF_TURNSTILE";
}
."${cfg.captcha.type}"
);
in
{
"cron.update_checker".ENABLED = lib.mkDefault false;
database = mkMerge [
{
DB_TYPE = cfg.database.type;
}
(mkIf (useMysql || usePostgresql) {
HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
NAME = cfg.database.name;
USER = cfg.database.user;
PASSWD = "#dbpass#";
})
(mkIf useSqlite {
PATH = cfg.database.path;
})
(mkIf usePostgresql {
SSL_MODE = "disable";
})
];
database = mkMerge [
{
DB_TYPE = cfg.database.type;
}
(mkIf (useMysql || usePostgresql) {
HOST =
if cfg.database.socket != null then
cfg.database.socket
else
cfg.database.host + ":" + toString cfg.database.port;
NAME = cfg.database.name;
USER = cfg.database.user;
PASSWD = "#dbpass#";
})
(mkIf useSqlite {
PATH = cfg.database.path;
})
(mkIf usePostgresql {
SSL_MODE = "disable";
})
];
repository = {
ROOT = cfg.repositoryRoot;
repository = {
ROOT = cfg.repositoryRoot;
};
server = mkIf cfg.lfs.enable {
LFS_START_SERVER = true;
LFS_JWT_SECRET = "#lfsjwtsecret#";
};
camo = mkIf (cfg.camoHmacKeyFile != null) {
HMAC_KEY = "#hmackey#";
};
session = {
COOKIE_NAME = lib.mkDefault "session";
};
security = {
SECRET_KEY = "#secretkey#";
INTERNAL_TOKEN = "#internaltoken#";
INSTALL_LOCK = true;
};
service = mkIf cfg.captcha.enable (mkMerge [
{
ENABLE_CAPTCHA = true;
CAPTCHA_TYPE = cfg.captcha.type;
REQUIRE_CAPTCHA_FOR_LOGIN = cfg.captcha.requireForLogin;
REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = cfg.captcha.requireForExternalRegistration;
}
(mkIf (cfg.captcha.secretFile != null) {
"${captchaPrefix}_SECRET" = "#captchasecret#";
})
(mkIf (cfg.captcha.siteKey != null) {
"${captchaPrefix}_SITEKEY" = cfg.captcha.siteKey;
})
(mkIf (cfg.captcha.url != null) {
"${captchaPrefix}_URL" = cfg.captcha.url;
})
]);
mailer = mkIf (cfg.mailerPasswordFile != null) {
PASSWD = "#mailerpass#";
};
metrics = mkIf (cfg.metricsTokenFile != null) {
TOKEN = "#metricstoken#";
};
oauth2 = {
JWT_SECRET = "#oauth2jwtsecret#";
};
lfs = mkIf cfg.lfs.enable {
PATH = cfg.lfs.contentDir;
};
packages.CHUNKED_UPLOAD_PATH = "${cfg.stateDir}/tmp/package-upload";
};
server = mkIf cfg.lfs.enable {
LFS_START_SERVER = true;
LFS_JWT_SECRET = "#lfsjwtsecret#";
};
camo = mkIf (cfg.camoHmacKeyFile != null) {
HMAC_KEY = "#hmackey#";
};
session = {
COOKIE_NAME = lib.mkDefault "session";
};
security = {
SECRET_KEY = "#secretkey#";
INTERNAL_TOKEN = "#internaltoken#";
INSTALL_LOCK = true;
};
service = mkIf cfg.captcha.enable (mkMerge [
{
ENABLE_CAPTCHA = true;
CAPTCHA_TYPE = cfg.captcha.type;
REQUIRE_CAPTCHA_FOR_LOGIN = cfg.captcha.requireForLogin;
REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = cfg.captcha.requireForExternalRegistration;
}
(mkIf (cfg.captcha.secretFile != null) {
"${captchaPrefix}_SECRET" = "#captchasecret#";
})
(mkIf (cfg.captcha.siteKey != null) {
"${captchaPrefix}_SITEKEY" = cfg.captcha.siteKey;
})
(mkIf (cfg.captcha.url != null) {
"${captchaPrefix}_URL" = cfg.captcha.url;
})
]);
mailer = mkIf (cfg.mailerPasswordFile != null) {
PASSWD = "#mailerpass#";
};
metrics = mkIf (cfg.metricsTokenFile != null) {
TOKEN = "#metricstoken#";
};
oauth2 = {
JWT_SECRET = "#oauth2jwtsecret#";
};
lfs = mkIf cfg.lfs.enable {
PATH = cfg.lfs.contentDir;
};
packages.CHUNKED_UPLOAD_PATH = "${cfg.stateDir}/tmp/package-upload";
};
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
enable = mkDefault true;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{ name = cfg.database.user;
{
name = cfg.database.user;
ensureDBOwnership = true;
}
];
@ -577,46 +678,60 @@ in
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{ name = cfg.database.user;
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
{
name = cfg.database.user;
ensurePermissions = {
"${cfg.database.name}.*" = "ALL PRIVILEGES";
};
}
];
};
systemd.tmpfiles.rules = [
"d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
systemd.tmpfiles.rules =
[
"d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
# If we have a folder or symlink with gitea locales, remove it
# And symlink the current gitea locales in place
"L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
# If we have a folder or symlink with gitea locales, remove it
# And symlink the current gitea locales in place
"L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
] ++ lib.optionals cfg.lfs.enable [
"d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
];
]
++ lib.optionals cfg.lfs.enable [
"d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
];
systemd.services.gitea = {
description = "gitea";
after = [ "network.target" ] ++ optional usePostgresql "postgresql.service" ++ optional useMysql "mysql.service";
requires = optional (cfg.database.createDatabase && usePostgresql) "postgresql.service" ++ optional (cfg.database.createDatabase && useMysql) "mysql.service";
after =
[ "network.target" ]
++ optional usePostgresql "postgresql.service"
++ optional useMysql "mysql.service";
requires =
optional (cfg.database.createDatabase && usePostgresql) "postgresql.service"
++ optional (cfg.database.createDatabase && useMysql) "mysql.service";
wantedBy = [ "multi-user.target" ];
path = [ cfg.package pkgs.git pkgs.gnupg ];
path = [
cfg.package
pkgs.git
pkgs.gnupg
];
# In older versions the secret naming for JWT was kind of confusing.
# The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
@ -625,85 +740,87 @@ in
# values for JWT_SECRET and the file jwt_secret gets renamed to
# lfs_jwt_secret.
# We have to consider this to stay compatible with older installations.
preStart = let
runConfig = "${cfg.customDir}/conf/app.ini";
secretKey = "${cfg.customDir}/conf/secret_key";
oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
internalToken = "${cfg.customDir}/conf/internal_token";
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
in ''
# copy custom configuration and generate random secrets if needed
${optionalString (!cfg.useWizard) ''
function gitea_setup {
cp -f '${configFile}' '${runConfig}'
preStart =
let
runConfig = "${cfg.customDir}/conf/app.ini";
secretKey = "${cfg.customDir}/conf/secret_key";
oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
internalToken = "${cfg.customDir}/conf/internal_token";
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
in
''
# copy custom configuration and generate random secrets if needed
${optionalString (!cfg.useWizard) ''
function gitea_setup {
cp -f '${configFile}' '${runConfig}'
if [ ! -s '${secretKey}' ]; then
${exe} generate secret SECRET_KEY > '${secretKey}'
fi
if [ ! -s '${secretKey}' ]; then
${exe} generate secret SECRET_KEY > '${secretKey}'
fi
# Migrate LFS_JWT_SECRET filename
if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
fi
# Migrate LFS_JWT_SECRET filename
if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
fi
if [ ! -s '${oauth2JwtSecret}' ]; then
${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
fi
if [ ! -s '${oauth2JwtSecret}' ]; then
${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
fi
${lib.optionalString cfg.lfs.enable ''
if [ ! -s '${lfsJwtSecret}' ]; then
${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
fi
''}
${lib.optionalString cfg.lfs.enable ''
if [ ! -s '${lfsJwtSecret}' ]; then
${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
fi
''}
if [ ! -s '${internalToken}' ]; then
${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
fi
if [ ! -s '${internalToken}' ]; then
${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
fi
chmod u+w '${runConfig}'
${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
chmod u+w '${runConfig}'
${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
${lib.optionalString cfg.lfs.enable ''
${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
''}
${lib.optionalString cfg.lfs.enable ''
${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
''}
${lib.optionalString (cfg.camoHmacKeyFile != null) ''
${replaceSecretBin} '#hmackey#' '${cfg.camoHmacKeyFile}' '${runConfig}'
''}
${lib.optionalString (cfg.camoHmacKeyFile != null) ''
${replaceSecretBin} '#hmackey#' '${cfg.camoHmacKeyFile}' '${runConfig}'
''}
${lib.optionalString (cfg.mailerPasswordFile != null) ''
${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
''}
${lib.optionalString (cfg.mailerPasswordFile != null) ''
${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
''}
${lib.optionalString (cfg.metricsTokenFile != null) ''
${replaceSecretBin} '#metricstoken#' '${cfg.metricsTokenFile}' '${runConfig}'
''}
${lib.optionalString (cfg.metricsTokenFile != null) ''
${replaceSecretBin} '#metricstoken#' '${cfg.metricsTokenFile}' '${runConfig}'
''}
${lib.optionalString (cfg.captcha.secretFile != null) ''
${replaceSecretBin} '#captchasecret#' '${cfg.captcha.secretFile}' '${runConfig}'
''}
chmod u-w '${runConfig}'
}
(umask 027; gitea_setup)
''}
${lib.optionalString (cfg.captcha.secretFile != null) ''
${replaceSecretBin} '#captchasecret#' '${cfg.captcha.secretFile}' '${runConfig}'
''}
chmod u-w '${runConfig}'
}
(umask 027; gitea_setup)
''}
# run migrations/init the database
${exe} migrate
# run migrations/init the database
${exe} migrate
# update all hooks' binary paths
${exe} admin regenerate hooks
# update all hooks' binary paths
${exe} admin regenerate hooks
# update command option in authorized_keys
if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
then
${exe} admin regenerate keys
fi
'';
# update command option in authorized_keys
if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
then
${exe} admin regenerate keys
fi
'';
serviceConfig = {
Type = "simple";
@ -719,7 +836,13 @@ in
ProcSubset = "pid";
ProtectProc = "invisible";
# Access write directories
ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
ReadWritePaths = [
cfg.customDir
cfg.dump.backupDir
cfg.repositoryRoot
cfg.stateDir
cfg.lfs.contentDir
];
UMask = "0027";
# Capabilities
CapabilityBoundingSet = "";
@ -737,7 +860,11 @@ in
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
@ -747,7 +874,10 @@ in
PrivateMounts = true;
# System Call Filtering
SystemCallArchitectures = "native";
SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
SystemCallFilter = [
"~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"
"setrlimit"
];
};
environment = {
@ -769,45 +899,51 @@ in
};
users.groups = mkIf (cfg.group == "gitea") {
gitea = {};
gitea = { };
};
warnings =
optional (cfg.database.password != "") "config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead." ++
optional (cfg.extraConfig != null) ''
optional (cfg.database.password != "")
"config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead."
++ optional (cfg.extraConfig != null) ''
services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
'' ++
optional (lib.getName cfg.package == "forgejo") ''
''
++ optional (lib.getName cfg.package == "forgejo") ''
Running forgejo via services.gitea.package is no longer supported.
Please use services.forgejo instead.
See https://nixos.org/manual/nixos/unstable/#module-forgejo for migration instructions.
'';
# Create database passwordFile default when password is configured.
services.gitea.database.passwordFile =
mkDefault (toString (pkgs.writeTextFile {
name = "gitea-database-password";
text = cfg.database.password;
}));
services.gitea.database.passwordFile = mkDefault (
toString (
pkgs.writeTextFile {
name = "gitea-database-password";
text = cfg.database.password;
}
)
);
systemd.services.gitea-dump = mkIf cfg.dump.enable {
description = "gitea dump";
after = [ "gitea.service" ];
path = [ cfg.package ];
description = "gitea dump";
after = [ "gitea.service" ];
path = [ cfg.package ];
environment = {
USER = cfg.user;
HOME = cfg.stateDir;
GITEA_WORK_DIR = cfg.stateDir;
GITEA_CUSTOM = cfg.customDir;
};
environment = {
USER = cfg.user;
HOME = cfg.stateDir;
GITEA_WORK_DIR = cfg.stateDir;
GITEA_CUSTOM = cfg.customDir;
};
serviceConfig = {
Type = "oneshot";
User = cfg.user;
ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
WorkingDirectory = cfg.dump.backupDir;
};
serviceConfig = {
Type = "oneshot";
User = cfg.user;
ExecStart =
"${exe} dump --type ${cfg.dump.type}"
+ optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
WorkingDirectory = cfg.dump.backupDir;
};
};
systemd.timers.gitea-dump = mkIf cfg.dump.enable {
@ -817,5 +953,9 @@ in
timerConfig.OnCalendar = cfg.dump.interval;
};
};
meta.maintainers = with lib.maintainers; [ ma27 techknowlogick SuperSandro2000 ];
meta.maintainers = with lib.maintainers; [
ma27
techknowlogick
SuperSandro2000
];
}

File diff suppressed because it is too large Load diff

View file

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

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