summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Hensing <robert@roberthensing.nl>2023-05-07 15:37:28 +0200
committerRobert Hensing <robert@roberthensing.nl>2023-05-10 15:55:09 +0200
commite5db80ae487b59b4e9f950d68983ffb0575e26c6 (patch)
treeb24eb183f8e6851d0a1daca9a702aa0c58780ae9
parent693e2c32871dcea7fe2ef455ee77571d3a117499 (diff)
nixosModules.pkgsReadOnly: init
-rw-r--r--flake.nix13
-rw-r--r--nixos/modules/misc/nixpkgs/read-only.nix74
-rw-r--r--nixos/modules/misc/nixpkgs/test.nix59
3 files changed, 146 insertions, 0 deletions
diff --git a/flake.nix b/flake.nix
index f9442d8ea2d2..fa00bffcdf92 100644
--- a/flake.nix
+++ b/flake.nix
@@ -57,6 +57,19 @@
nixosModules = {
notDetected = ./nixos/modules/installer/scan/not-detected.nix;
+
+ /*
+ Make the `nixpkgs.*` configuration read-only. Guarantees that `pkgs`
+ is the way you initialize it.
+
+ Example:
+
+ {
+ imports = [ nixpkgs.nixosModules.readOnlyPkgs ];
+ nixpkgs.pkgs = nixpkgs.legacyPackages.x86_64-linux;
+ }
+ */
+ readOnlyPkgs = ./nixos/modules/misc/nixpkgs/read-only.nix;
};
};
}
diff --git a/nixos/modules/misc/nixpkgs/read-only.nix b/nixos/modules/misc/nixpkgs/read-only.nix
new file mode 100644
index 000000000000..2a783216a9d5
--- /dev/null
+++ b/nixos/modules/misc/nixpkgs/read-only.nix
@@ -0,0 +1,74 @@
+# A replacement for the traditional nixpkgs module, such that none of the modules
+# can add their own configuration. This ensures that the Nixpkgs configuration is
+# exactly as the user intends.
+# This may also be used as a performance optimization when evaluating multiple
+# configurations at once, with a shared `pkgs`.
+
+# This is a separate module, because merging this logic into the nixpkgs module
+# is too burdensome, considering that it is already burdened with legacy.
+# Moving this logic into a module does not lose any composition benefits, because
+# its purpose is not something that composes anyway.
+
+{ lib, config, ... }:
+
+let
+ cfg = config.nixpkgs;
+ inherit (lib) mkOption types;
+
+in
+{
+ disabledModules = [
+ ../nixpkgs.nix
+ ];
+ options = {
+ nixpkgs = {
+ pkgs = mkOption {
+ type = lib.types.pkgs;
+ description = lib.mdDoc ''The pkgs module argument.'';
+ };
+ config = mkOption {
+ internal = true;
+ type = types.unique { message = "nixpkgs.config is set to read-only"; } types.anything;
+ description = lib.mdDoc ''
+ The Nixpkgs `config` that `pkgs` was initialized with.
+ '';
+ };
+ overlays = mkOption {
+ internal = true;
+ type = types.unique { message = "nixpkgs.overlays is set to read-only"; } types.anything;
+ description = lib.mdDoc ''
+ The Nixpkgs overlays that `pkgs` was initialized with.
+ '';
+ };
+ hostPlatform = mkOption {
+ internal = true;
+ readOnly = true;
+ description = lib.mdDoc ''
+ The platform of the machine that is running the NixOS configuration.
+ '';
+ };
+ buildPlatform = mkOption {
+ internal = true;
+ readOnly = true;
+ description = lib.mdDoc ''
+ The platform of the machine that built the NixOS configuration.
+ '';
+ };
+ # NOTE: do not add the legacy options such as localSystem here. Let's keep
+ # this module simple and let module authors upgrade their code instead.
+ };
+ };
+ config = {
+ _module.args.pkgs =
+ # find mistaken definitions
+ builtins.seq cfg.config
+ builtins.seq cfg.overlays
+ builtins.seq cfg.hostPlatform
+ builtins.seq cfg.buildPlatform
+ cfg.pkgs;
+ nixpkgs.config = cfg.pkgs.config;
+ nixpkgs.overlays = cfg.pkgs.overlays;
+ nixpkgs.hostPlatform = cfg.pkgs.stdenv.hostPlatform;
+ nixpkgs.buildPlatform = cfg.pkgs.stdenv.buildPlatform;
+ };
+}
diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix
index a6d8877ae070..0536cfc9624a 100644
--- a/nixos/modules/misc/nixpkgs/test.nix
+++ b/nixos/modules/misc/nixpkgs/test.nix
@@ -1,3 +1,5 @@
+# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace
+
{ evalMinimalConfig, pkgs, lib, stdenv }:
let
eval = mod: evalMinimalConfig {
@@ -27,6 +29,47 @@ let
let
uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);
+
+ readOnlyUndefined = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ };
+
+ readOnlyBad = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = { };
+ };
+
+ readOnly = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = pkgs;
+ };
+
+ readOnlyBadConfig = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = pkgs;
+ nixpkgs.config.allowUnfree = true; # do in pkgs instead!
+ };
+
+ readOnlyBadOverlays = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = pkgs;
+ nixpkgs.overlays = [ (_: _: {}) ]; # do in pkgs instead!
+ };
+
+ readOnlyBadHostPlatform = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = pkgs;
+ nixpkgs.hostPlatform = "foo-linux"; # do in pkgs instead!
+ };
+
+ readOnlyBadBuildPlatform = evalMinimalConfig {
+ imports = [ ./read-only.nix ];
+ nixpkgs.pkgs = pkgs;
+ nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
+ };
+
+ throws = x: ! (builtins.tryEval x).success;
+
in
lib.recurseIntoAttrs {
invokeNixpkgsSimple =
@@ -65,5 +108,21 @@ lib.recurseIntoAttrs {
nixpkgs.pkgs = pkgs;
} == [];
+
+ # Tests for the read-only.nix module
+ assert readOnly._module.args.pkgs.stdenv.hostPlatform.system == pkgs.stdenv.hostPlatform.system;
+ assert throws readOnlyBad._module.args.pkgs.stdenv;
+ assert throws readOnlyUndefined._module.args.pkgs.stdenv;
+ assert throws readOnlyBadConfig._module.args.pkgs.stdenv;
+ assert throws readOnlyBadOverlays._module.args.pkgs.stdenv;
+ assert throws readOnlyBadHostPlatform._module.args.pkgs.stdenv;
+ assert throws readOnlyBadBuildPlatform._module.args.pkgs.stdenv;
+ # read-only.nix does not provide legacy options, for the sake of simplicity
+ # If you're bothered by this, upgrade your configs to use the new *Platform
+ # options.
+ assert !readOnly.options.nixpkgs?system;
+ assert !readOnly.options.nixpkgs?localSystem;
+ assert !readOnly.options.nixpkgs?crossSystem;
+
pkgs.emptyFile;
}