nixosModules.pkgsReadOnly: init

This commit is contained in:
Robert Hensing 2023-05-07 15:37:28 +02:00
parent 693e2c3287
commit e5db80ae48
3 changed files with 146 additions and 0 deletions

View File

@ -57,6 +57,19 @@
nixosModules = { nixosModules = {
notDetected = ./nixos/modules/installer/scan/not-detected.nix; 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;
}; };
}; };
} }

View File

@ -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;
};
}

View File

@ -1,3 +1,5 @@
# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace
{ evalMinimalConfig, pkgs, lib, stdenv }: { evalMinimalConfig, pkgs, lib, stdenv }:
let let
eval = mod: evalMinimalConfig { eval = mod: evalMinimalConfig {
@ -27,6 +29,47 @@ let
let let
uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; }; uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions); 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 in
lib.recurseIntoAttrs { lib.recurseIntoAttrs {
invokeNixpkgsSimple = invokeNixpkgsSimple =
@ -65,5 +108,21 @@ lib.recurseIntoAttrs {
nixpkgs.pkgs = pkgs; 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; pkgs.emptyFile;
} }