diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6526b8203..f8078c495 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,9 @@ /modules/misc/news.nix @rycee +/modules/misc/nix.nix @polykernel +/tests/modules/misc/nix @polykernel + /modules/misc/nixpkgs-disabled.nix @thiagokokada /modules/misc/numlock.nix @evanjs diff --git a/modules/misc/nix.nix b/modules/misc/nix.nix index a044400fa..e722c7d99 100644 --- a/modules/misc/nix.nix +++ b/modules/misc/nix.nix @@ -6,8 +6,98 @@ let cfg = config.nix; + nixPackage = cfg.package; + + isNixAtLeast = versionAtLeast (getVersion nixPackage); + + nixConf = 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 isCoercibleToString v then + toString v + else + abort "The nix conf value: ${toPretty { } v} can not be encoded"; + + mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}"; + + mkKeyValuePairs = attrs: + concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs); + + in pkgs.writeTextFile { + name = "nix.conf"; + text = '' + # WARNING: this file is generated from the nix.settings option in + # your Home Manager configuration at $XDG_CONFIG_HOME/nix/nix.conf. + # Do not edit it! + ${mkKeyValuePairs cfg.settings} + ${cfg.extraOptions} + ''; + checkPhase = + if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then '' + echo "Ignoring validation for cross-compilation" + '' else '' + echo "Validating generated nix.conf" + ln -s $out ./nix.conf + set -e + set +o pipefail + NIX_CONF_DIR=$PWD \ + ${cfg.package}/bin/nix show-config ${ + optionalString (isNixAtLeast "2.3pre") + "--no-net --option experimental-features nix-command" + } \ + |& sed -e 's/^warning:/error:/' \ + | (! grep '${ + if cfg.checkConfig then "^error:" else "^error: unknown setting" + }') + set -o pipefail + ''; + }; + + semanticConfType = with types; + let + confAtom = nullOr (oneOf [ bool int float str path package ]) // { + description = + "Nix config atom (null, bool, int, float, str, path or package)"; + }; + in attrsOf (either confAtom (listOf confAtom)); + + jsonFormat = pkgs.formats.json { }; + in { options.nix = { + enable = mkEnableOption '' + the Nix configuration module + '' // { + default = true; + visible = false; + }; + + package = mkOption { + type = types.nullOr types.package; + default = null; + example = literalExpression "pkgs.nix"; + description = '' + The Nix package that the configuration should be generated for. + ''; + }; + registry = mkOption { type = types.attrsOf (types.submodule (let inputAttrs = types.attrsOf @@ -68,19 +158,81 @@ in { User level flake registry. ''; }; + registryVersion = mkOption { type = types.int; default = 2; internal = true; description = "The flake registry format version."; }; - }; - config = mkIf (cfg.registry != { }) { - xdg.configFile."nix/registry.json".text = builtins.toJSON { - version = cfg.registryVersion; - flakes = - mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry; + checkConfig = mkOption { + type = types.bool; + default = true; + description = '' + If enabled (the default), checks for data type mismatches and that Nix + can parse the generated nix.conf. + ''; + }; + + extraOptions = mkOption { + type = types.lines; + default = ""; + example = '' + keep-outputs = true + keep-derivations = true + ''; + description = + "Additional text appended to nix.conf."; + }; + + settings = mkOption { + type = types.submodule { freeformType = semanticConfType; }; + default = { }; + example = literalExpression '' + { + use-sandbox = true; + show-trace = true; + system-features = [ "big-parallel" "kvm" "recursive-nix" ]; + } + ''; + description = '' + Configuration for Nix, see + or + + nix.conf + 5 + for avalaible options. + The value declared here will be translated directly to the key-value pairs Nix expects. + + + Configuration specified in which will be appended + verbatim to the resulting config file. + ''; }; }; + + config = mkIf cfg.enable { + assertions = [{ + assertion = cfg.settings == { } || cfg.package != null; + message = '' + A corresponding Nix package must be specified via `nix.package` for generating + nix.conf. + ''; + }]; + + xdg.configFile = { + "nix/registry.json" = mkIf (cfg.registry != { }) { + source = jsonFormat.generate "registry.json" { + version = cfg.registryVersion; + flakes = + mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry; + }; + }; + + "nix/nix.conf" = mkIf (cfg.settings != { }) { source = nixConf; }; + }; + }; + + meta.maintainers = [ maintainers.polykernel ]; } diff --git a/tests/default.nix b/tests/default.nix index a6acb8e2d..3f93dc9c1 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -45,6 +45,7 @@ import nmt { ./modules/files ./modules/home-environment ./modules/misc/fontconfig + ./modules/misc/nix ./modules/programs/alacritty ./modules/programs/alot ./modules/programs/aria2 diff --git a/tests/modules/misc/nix/default.nix b/tests/modules/misc/nix/default.nix new file mode 100644 index 000000000..b0370c2e4 --- /dev/null +++ b/tests/modules/misc/nix/default.nix @@ -0,0 +1,5 @@ +{ + nix-empty-settings = ./empty-settings.nix; + nix-example-settings = ./example-settings.nix; + nix-example-registry = ./example-registry.nix; +} diff --git a/tests/modules/misc/nix/empty-settings.nix b/tests/modules/misc/nix/empty-settings.nix new file mode 100644 index 000000000..18f1ab430 --- /dev/null +++ b/tests/modules/misc/nix/empty-settings.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + nix = { package = config.lib.test.mkStubPackage { }; }; + + nmt.script = '' + assertPathNotExists home-files/.config/nix + ''; + }; +} diff --git a/tests/modules/misc/nix/example-registry-expected.json b/tests/modules/misc/nix/example-registry-expected.json new file mode 100644 index 000000000..cf5cee229 --- /dev/null +++ b/tests/modules/misc/nix/example-registry-expected.json @@ -0,0 +1,17 @@ +{ + "flakes": [ + { + "exact": true, + "from": { + "id": "nixpkgs", + "type": "indirect" + }, + "to": { + "owner": "my-org", + "repo": "my-nixpkgs", + "type": "github" + } + } + ], + "version": 2 +} diff --git a/tests/modules/misc/nix/example-registry.nix b/tests/modules/misc/nix/example-registry.nix new file mode 100644 index 000000000..1875b34d3 --- /dev/null +++ b/tests/modules/misc/nix/example-registry.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + nix = { + registry = { + nixpkgs = { + to = { + type = "github"; + owner = "my-org"; + repo = "my-nixpkgs"; + }; + }; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/nix/registry.json \ + ${./example-registry-expected.json} + ''; + }; +} diff --git a/tests/modules/misc/nix/example-settings-expected.conf b/tests/modules/misc/nix/example-settings-expected.conf new file mode 100644 index 000000000..2c2587fd6 --- /dev/null +++ b/tests/modules/misc/nix/example-settings-expected.conf @@ -0,0 +1,7 @@ +# WARNING: this file is generated from the nix.settings option in +# your Home Manager configuration at $XDG_CONFIG_HOME/nix/nix.conf. +# Do not edit it! +show-trace = true +system-features = big-parallel kvm recursive-nix +use-sandbox = true + diff --git a/tests/modules/misc/nix/example-settings.nix b/tests/modules/misc/nix/example-settings.nix new file mode 100644 index 000000000..f3f9e647e --- /dev/null +++ b/tests/modules/misc/nix/example-settings.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + nix = { + package = config.lib.test.mkStubPackage { + version = lib.getVersion pkgs.nixStable; + buildScript = '' + target=$out/bin/nix + mkdir -p "$(dirname "$target")" + + echo -n "true" > "$target" + + chmod +x "$target" + ''; + }; + + settings = { + use-sandbox = true; + show-trace = true; + system-features = [ "big-parallel" "kvm" "recursive-nix" ]; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/nix/nix.conf \ + ${./example-settings-expected.conf} + ''; + }; +}