From 842d7ae411da007e37f280e8026800a492fba249 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Thu, 9 Oct 2025 18:44:21 +0100 Subject: [PATCH 01/13] nixos-rebuild-ng: do not handle build exceptions in re-exec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Classic `nixos-rebuild` bails-out if re-exec fails, but `nixos-rebuild-ng` tries to keep going. The problem this is causing is that failures in re-exec evaluation almost always are errors in the user configuration, for example: ```diff diff --git i/modules/nixos/default.nix w/modules/nixos/default.nix index 1d8ef762..cb7e2c9d 100644 --- i/modules/nixos/default.nix +++ w/modules/nixos/default.nix @@ -11,6 +11,6 @@ ./nix ./server ./system - ./window-manager + ./window-manager; ]; } ``` And if you try to run, you get the following output: ``` $ nixos-rebuild switch warning: Git tree '/etc/nixos' is dirty error: … while calling the 'seq' builtin at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:361:18: 360| options = checked options; 361| config = checked (removeAttrs config [ "_module" ]); | ^ 362| _module = checked (config._module); … while evaluating a branch condition at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:297:9: 296| checkUnmatched = 297| if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then | ^ 298| let (stack trace truncated; use '--show-trace' to show the full, detailed trace) error: syntax error, unexpected ';' at /nix/store/c7zc5zg8c2hfnk7ihxaq574c83kjqav3-source/modules/nixos/default.nix:14:21: 13| ./system 14| ./window-manager; | ^ 15| ]; warning: could not build a newer version of nixos-rebuild, using current version building the system configuration... warning: Git tree '/etc/nixos' is dirty error: … while calling the 'seq' builtin at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:361:18: 360| options = checked options; 361| config = checked (removeAttrs config [ "_module" ]); | ^ 362| _module = checked (config._module); … while evaluating a branch condition at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:297:9: 296| checkUnmatched = 297| if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then | ^ 298| let (stack trace truncated; use '--show-trace' to show the full, detailed trace) error: syntax error, unexpected ';' at /nix/store/c7zc5zg8c2hfnk7ihxaq574c83kjqav3-source/modules/nixos/default.nix:14:21: 13| ./system 14| ./window-manager; | ^ 15| ]; Command 'nix --extra-experimental-features 'nix-command flakes' build --print-out-paths '/etc/nixos#nixosConfigurations."blackrockshooter-nixos".config.system.build.toplevel' --no-link' returned non-zero exit status 1. ``` The error is printed twice because we first try to eval (and fail) for re-exec and since we keep going the actual build also fails. Since classic `nixos-rebuild` bails out in the re-exec eval failure, it should be safe to not handle the exception here. --- .../src/nixos_rebuild/services.py | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py index 0e47f5f0cd50..d99089a44d34 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py @@ -4,7 +4,6 @@ import logging import os import sys from pathlib import Path -from subprocess import CalledProcessError from typing import Final from . import nix, tmpdir @@ -26,26 +25,22 @@ def reexec( flake_build_flags: Args, ) -> None: drv = None - try: - # Parsing the args here but ignore ask_sudo_password since it is not - # needed and we would end up asking sudo password twice - if flake := Flake.from_arg(args.flake, Remote.from_arg(args.target_host, None)): - drv = nix.build_flake( - NIXOS_REBUILD_ATTR, - flake, - flake_build_flags | {"no_link": True}, - ) - else: - build_attr = BuildAttr.from_arg(args.attr, args.file) - drv = nix.build( - NIXOS_REBUILD_ATTR, - build_attr, - build_flags | {"no_out_link": True}, - ) - except CalledProcessError: - logger.warning( - "could not build a newer version of nixos-rebuild, using current version", - exc_info=logger.isEnabledFor(logging.DEBUG), + # Parsing the args here but ignore ask_sudo_password since it is not + # needed and we would end up asking sudo password twice + if flake := Flake.from_arg( + args.flake, Remote.from_arg(args.target_host, ask_sudo_password=None) + ): + drv = nix.build_flake( + NIXOS_REBUILD_ATTR, + flake, + flake_build_flags | {"no_link": True}, + ) + else: + build_attr = BuildAttr.from_arg(args.attr, args.file) + drv = nix.build( + NIXOS_REBUILD_ATTR, + build_attr, + build_flags | {"no_out_link": True}, ) if drv: From c6f05840a43144a72b385f7dd263140e76a8ccbf Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Thu, 9 Oct 2025 19:01:12 +0100 Subject: [PATCH 02/13] nixos-rebuild-ng: move _NIXOS_REBUILD_REEXEC to services.reexec --- .../src/nixos_rebuild/__init__.py | 8 +------ .../src/nixos_rebuild/services.py | 8 +++++-- .../src/tests/test_services.py | 21 +++++++++++++++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py index 78f30989ba02..6d7ad01baebd 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py @@ -1,6 +1,5 @@ import argparse import logging -import os import sys from subprocess import CalledProcessError, run from typing import Final, assert_never @@ -290,12 +289,7 @@ def execute(argv: list[str]) -> None: # Re-exec to a newer version of the script before building to ensure we get # the latest fixes - if ( - WITH_REEXEC - and can_run - and not args.no_reexec - and not os.environ.get("_NIXOS_REBUILD_REEXEC") - ): + if WITH_REEXEC and can_run and not args.no_reexec: services.reexec(argv, args, build_flags, flake_build_flags) profile = Profile.from_arg(args.profile_name) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py index d99089a44d34..b145f735c283 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py @@ -13,6 +13,7 @@ from .process import Remote, cleanup_ssh from .utils import Args, tabulate NIXOS_REBUILD_ATTR: Final = "config.system.build.nixos-rebuild" +NIXOS_REBUILD_REEXEC_ENV: Final = "_NIXOS_REBUILD_REEXEC" logger: Final = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -24,6 +25,9 @@ def reexec( build_flags: Args, flake_build_flags: Args, ) -> None: + if os.environ.get(NIXOS_REBUILD_REEXEC_ENV): + return + drv = None # Parsing the args here but ignore ask_sudo_password since it is not # needed and we would end up asking sudo password twice @@ -57,7 +61,7 @@ def reexec( cleanup_ssh() tmpdir.TMPDIR.cleanup() try: - os.execve(new, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"}) + os.execve(new, argv, os.environ | {NIXOS_REBUILD_REEXEC_ENV: "1"}) except Exception: # Possible errors that we can have here: # - Missing the binary @@ -69,7 +73,7 @@ def reexec( ) # We already run clean-up, let's re-exec in the current version # to avoid issues - os.execve(current, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"}) + os.execve(current, argv, os.environ | {NIXOS_REBUILD_REEXEC_ENV: "1"}) def _validate_image_variant(image_variant: str, variants: ImageVariants) -> None: diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py index 15907f21fe3c..655c127c2744 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_services.py @@ -39,7 +39,7 @@ def test_reexec(mock_build: Mock, mock_execve: Mock, monkeypatch: MonkeyPatch) - mock_execve.assert_called_once_with( Path("/path/new/bin/nixos-rebuild-ng"), ["/path/bin/nixos-rebuild-ng", "switch", "--no-flake"], - {"_NIXOS_REBUILD_REEXEC": "1"}, + {s.NIXOS_REBUILD_REEXEC_ENV: "1"}, ) mock_execve.reset_mock() @@ -50,7 +50,7 @@ def test_reexec(mock_build: Mock, mock_execve: Mock, monkeypatch: MonkeyPatch) - mock_execve.assert_any_call( Path("/path/bin/nixos-rebuild-ng"), ["/path/bin/nixos-rebuild-ng", "switch", "--no-flake"], - {"_NIXOS_REBUILD_REEXEC": "1"}, + {s.NIXOS_REBUILD_REEXEC_ENV: "1"}, ) @@ -81,7 +81,7 @@ def test_reexec_flake( mock_execve.assert_called_once_with( Path("/path/new/bin/nixos-rebuild-ng"), ["/path/bin/nixos-rebuild-ng", "switch", "--flake"], - {"_NIXOS_REBUILD_REEXEC": "1"}, + {s.NIXOS_REBUILD_REEXEC_ENV: "1"}, ) mock_execve.reset_mock() @@ -92,5 +92,18 @@ def test_reexec_flake( mock_execve.assert_any_call( Path("/path/bin/nixos-rebuild-ng"), ["/path/bin/nixos-rebuild-ng", "switch", "--flake"], - {"_NIXOS_REBUILD_REEXEC": "1"}, + {s.NIXOS_REBUILD_REEXEC_ENV: "1"}, ) + + +@patch.dict(os.environ, {s.NIXOS_REBUILD_REEXEC_ENV: "1"}, clear=True) +@patch("os.execve", autospec=True) +@patch(get_qualified_name(s.nix.build_flake), autospec=True) +def test_reexec_skip_if_already_reexec(mock_build: Mock, mock_execve: Mock) -> None: + argv = ["/path/bin/nixos-rebuild-ng", "switch", "--flake"] + args, _ = n.parse_args(argv) + mock_build.return_value = Path("/path") + + s.reexec(argv, args, {"build": True}, {"flake": True}) + mock_build.assert_not_called() + mock_execve.assert_not_called() From e8b9d879ce13b37915bf3b7988976fdbbfe45c9c Mon Sep 17 00:00:00 2001 From: nikstur Date: Sun, 28 Sep 2025 20:44:11 +0200 Subject: [PATCH 03/13] nixos/shadow: use /bin/sh as default userDefaultShell The default was previously set by the bash module. It's not set directly here (to the compiled in value) of the shadow package to allow disabling the bash module. Note that this doesn't mean that the effective default shell of a normal NixOS installation has changed. It is still bash. --- nixos/modules/programs/shadow.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix index 049d5d0c78a7..12c9a84333bf 100644 --- a/nixos/modules/programs/shadow.nix +++ b/nixos/modules/programs/shadow.nix @@ -158,6 +158,8 @@ in This must not be a store path, since the path is used outside the store (in particular in /etc/passwd). ''; + # /bin/sh is also the compiled in default of the shadow package. + default = "/bin/sh"; example = lib.literalExpression "pkgs.zsh"; type = lib.types.either lib.types.path lib.types.shellPackage; }; From 70852ab2da9b528aaa86ceb17d379f68e3817424 Mon Sep 17 00:00:00 2001 From: nikstur Date: Sun, 28 Sep 2025 20:52:53 +0200 Subject: [PATCH 04/13] nixos/shells-environment: add environment.shell.enable option --- nixos/modules/config/shells-environment.nix | 29 +++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix index a3ee4d6507f9..0623ae6fdaeb 100644 --- a/nixos/modules/config/shells-environment.nix +++ b/nixos/modules/config/shells-environment.nix @@ -36,6 +36,24 @@ in options = { + environment.shell.enable = lib.mkEnableOption "" // { + default = true; + internal = true; + description = '' + Whether to enable the shell environment. + + This does NOT necessarily disable all shells on your system. Instead it + just disables the shell environment that configures user shells (i.e. + those in `environment.shells`). + + System services might still depend on and use shells even if this + option is set to false. + + Only set this option if you're sure that you can recover from potential + issues. + ''; + }; + environment.variables = lib.mkOption { default = { }; example = { @@ -175,10 +193,10 @@ in }; environment.binsh = lib.mkOption { - default = "${config.system.build.binsh}/bin/sh"; - defaultText = lib.literalExpression ''"''${config.system.build.binsh}/bin/sh"''; + default = null; + defaultText = lib.literalExpression ''"''${pkgs.bashInteractive}/bin/sh"''; example = lib.literalExpression ''"''${pkgs.dash}/bin/dash"''; - type = lib.types.path; + type = lib.types.nullOr lib.types.path; visible = false; description = '' The shell executable that is linked system-wide to @@ -201,9 +219,10 @@ in }; - config = { + config = lib.mkIf cfg.shell.enable { - system.build.binsh = pkgs.bashInteractive; + # Set this here so its enabled only when shell support is enabled + environment.binsh = lib.mkDefault "${pkgs.bashInteractive}/bin/sh"; # Set session variables in the shell as well. This is usually # unnecessary, but it allows changes to session variables to take From 26cf6ffe54291ae6e31546524ca5b2b70b2c9c42 Mon Sep 17 00:00:00 2001 From: nikstur Date: Thu, 21 Aug 2025 01:01:40 +0200 Subject: [PATCH 05/13] nixos/top-level: only include preSwitchChecks when they are set --- nixos/modules/system/activation/top-level.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index c218bb15ba14..9a1cde27bf77 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -376,7 +376,9 @@ in ); # End if legacy environment variables - preSwitchCheck = config.system.preSwitchChecksScript; + preSwitchCheck = lib.mkIf ( + config.system.preSwitchChecks != { } + ) config.system.preSwitchChecksScript; # Not actually used in the builder. `passedChecks` is just here to create # the build dependencies. Checks are similar to build dependencies in the From 8002da18ab78b9d93db8c776a99fcab2ac529511 Mon Sep 17 00:00:00 2001 From: nikstur Date: Sun, 28 Sep 2025 20:59:28 +0200 Subject: [PATCH 06/13] nixos/systemd-initrd: add `boot.initrd.systemd.shell.enable` option --- nixos/modules/system/boot/systemd/initrd.nix | 35 +++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 761d05fc873e..f2b1b4fc7ede 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -325,6 +325,22 @@ in ''; }; + shell.enable = lib.mkEnableOption "" // { + default = config.environment.shell.enable; + internal = true; + description = '' + Whether to enable a shell in the initrd. + + In contrast to `environment.shell.enable`, this option actually + strictly disables all shells in the initrd because they're not copied + into it anymore. Paths that use a shell (e.g. via the `script` option), + will break if this option is set. + + Only set this option if you're sure that you can recover from potential + issues. + ''; + }; + units = mkOption { description = "Definition of systemd units."; default = { }; @@ -460,13 +476,15 @@ in ++ lib.optional (config.boot.initrd.systemd.root == "gpt-auto") "rw"; boot.initrd.systemd = { - # bashInteractive is easier to use and also required by debug-shell.service initrdBin = [ - pkgs.bashInteractive pkgs.coreutils cfg.package ] - ++ lib.optional (config.system.build.kernel.config.isYes "MODULES") cfg.package.kmod; + ++ lib.optional (config.system.build.kernel.config.isYes "MODULES") cfg.package.kmod + ++ lib.optionals cfg.shell.enable [ + # bashInteractive is easier to use and also required by debug-shell.service + pkgs.bashInteractive + ]; extraBin = { less = "${pkgs.less}/bin/less"; mount = "${cfg.package.util-linux}/bin/mount"; @@ -550,11 +568,6 @@ in "${cfg.package.util-linux}/bin/umount" "${cfg.package.util-linux}/bin/sulogin" - # required for services generated with writeShellScript and friends - pkgs.runtimeShell - # some tools like xfs still want the sh symlink - "${pkgs.bashNonInteractive}/bin" - # so NSS can look up usernames "${pkgs.glibc}/lib/libnss_files.so.2" @@ -566,6 +579,12 @@ in ++ lib.optionals config.system.nixos-init.enable [ "${config.system.nixos-init.package}/bin/initrd-init" ] + ++ lib.optionals cfg.shell.enable [ + # required for services generated with writeShellScript and friends + pkgs.runtimeShell + # some tools like xfs still want the sh symlink + "${pkgs.bashNonInteractive}/bin" + ] ++ jobScripts ++ map (c: builtins.removeAttrs c [ "text" ]) (builtins.attrValues cfg.contents); From 9cff1cca05114447083dcd3eb121beddf94c1eff Mon Sep 17 00:00:00 2001 From: nikstur Date: Thu, 9 Oct 2025 22:03:33 +0200 Subject: [PATCH 07/13] nixos/test-instrumentation: add backdoor option This allows using the test-instrumentation without the backdoor. This is useful for example for bashless tests. --- nixos/modules/testing/test-instrumentation.nix | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix index 9fb3ad6ab774..8461a7472d2b 100644 --- a/nixos/modules/testing/test-instrumentation.nix +++ b/nixos/modules/testing/test-instrumentation.nix @@ -85,6 +85,9 @@ in { options.testing = { + backdoor = lib.mkEnableOption "backdoor service in stage 2" // { + default = true; + }; initrdBackdoor = lib.mkEnableOption '' backdoor.service in initrd. Requires @@ -107,12 +110,14 @@ in } ]; - systemd.services.backdoor = lib.mkMerge [ - backdoorService - { - wantedBy = [ "multi-user.target" ]; - } - ]; + systemd.services.backdoor = lib.mkIf cfg.backdoor ( + lib.mkMerge [ + backdoorService + { + wantedBy = [ "multi-user.target" ]; + } + ] + ); boot.initrd.systemd = lib.mkMerge [ { From 0e0be1398ad9889dbae82866060614bf540510b9 Mon Sep 17 00:00:00 2001 From: nikstur Date: Sun, 27 Jul 2025 21:18:56 +0200 Subject: [PATCH 08/13] nixos/profiles: add bashless profile --- nixos/modules/profiles/bashless.nix | 46 +++++++++ nixos/tests/activation/bashless-closure.nix | 78 ++++++++++++++ nixos/tests/activation/bashless-image.nix | 109 ++++++++++++++++++++ nixos/tests/activation/bashless.nix | 50 +++++++++ nixos/tests/all-tests.nix | 3 + 5 files changed, 286 insertions(+) create mode 100644 nixos/modules/profiles/bashless.nix create mode 100644 nixos/tests/activation/bashless-closure.nix create mode 100644 nixos/tests/activation/bashless-image.nix create mode 100644 nixos/tests/activation/bashless.nix diff --git a/nixos/modules/profiles/bashless.nix b/nixos/modules/profiles/bashless.nix new file mode 100644 index 000000000000..b6764a92f4f8 --- /dev/null +++ b/nixos/modules/profiles/bashless.nix @@ -0,0 +1,46 @@ +{ lib, ... }: + +{ + + # Bashless builds on perlless + imports = [ ./perlless.nix ]; + + # Remove bash from activation + system.nixos-init.enable = lib.mkDefault true; + system.activatable = lib.mkDefault false; + environment.shell.enable = lib.mkDefault false; + programs.bash.enable = lib.mkDefault false; + + # Random bash remnants + environment.corePackages = lib.mkForce [ ]; + # Contains bash completions + nix.enable = lib.mkDefault false; + # The fuse{,3} package contains a runtime dependency on bash. + programs.fuse.enable = lib.mkDefault false; + documentation.man.man-db.enable = lib.mkDefault false; + # autovt depends on bash + console.enable = lib.mkDefault false; + # dhcpcd and openresolv depend on bash + networking.useNetworkd = lib.mkDefault true; + # bcache tools depend on bash. + boot.bcache.enable = lib.mkDefault false; + # iptables depends on bash and nixos-firewall-tool is a bash script + networking.firewall.enable = lib.mkDefault false; + # the wrapper script is in bash + security.enableWrappers = lib.mkDefault false; + # kexec script is written in bash + boot.kexec.enable = lib.mkDefault false; + # Relies on bash scripts + powerManagement.enable = lib.mkDefault false; + # Has some bash inside + systemd.shutdownRamfs.enable = lib.mkDefault false; + # Relies on the gzip command which depends on bash + services.logrotate.enable = lib.mkDefault false; + # Service relies on bash scripts + services.timesyncd.enable = lib.mkDefault false; + + # Check that the system does not contain a Nix store path that contains the + # string "bash". + system.forbiddenDependenciesRegexes = [ "bash" ]; + +} diff --git a/nixos/tests/activation/bashless-closure.nix b/nixos/tests/activation/bashless-closure.nix new file mode 100644 index 000000000000..660090dd30c3 --- /dev/null +++ b/nixos/tests/activation/bashless-closure.nix @@ -0,0 +1,78 @@ +{ + nixos, + stdenvNoCC, + jq, + zstd, + cpio, +}: + +let + machine = nixos ( + { lib, modulesPath, ... }: + { + imports = [ "${modulesPath}/profiles/bashless.nix" ]; + + fileSystems."/" = { + device = "/dev/disk/by-partlabel/root"; + fsType = "ext4"; + }; + + system.stateVersion = lib.trivial.release; + } + ); +in +{ + # Keep this around for easier debugging, e.g. with nix why-depends. + inherit (machine) toplevel; + + machine = stdenvNoCC.mkDerivation { + name = "bashless-closure-machine"; + + __structuredAttrs = true; + + exportReferencesGraph.closure = [ machine.toplevel ]; + + preferLocalBuild = true; + + nativeBuildInputs = [ + jq + ]; + + buildCommand = '' + set +e + jq -r '.closure[].path' < "$NIX_ATTRS_JSON_FILE" | grep bash + + exit_code=$? + if [ $exit_code -eq 0 ]; then + echo "Error: toplevel contains bash" + exit 1 + fi + + touch $out + ''; + }; + + initrd = stdenvNoCC.mkDerivation { + name = "bashless-closure-initrd"; + + preferLocalBuild = true; + + nativeBuildInputs = [ + zstd + cpio + ]; + + buildCommand = '' + set +e + zstd -dfc ${machine.toplevel}/initrd | cpio --quiet -t | grep bash + + exit_code=$? + if [ $exit_code -eq 0 ]; then + echo "Error: initrd contains bash" + exit 1 + fi + + touch $out + ''; + }; +} diff --git a/nixos/tests/activation/bashless-image.nix b/nixos/tests/activation/bashless-image.nix new file mode 100644 index 000000000000..9b5f868ac6a0 --- /dev/null +++ b/nixos/tests/activation/bashless-image.nix @@ -0,0 +1,109 @@ +# Tests a bashless image. The forbiddenDependenciesRegexes from the bashless +# profile ensures that the closure doesn't contain any bash. + +{ lib, ... }: + +let + storePartitionLabel = "root"; +in +{ + + name = "activation-bashless-image"; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + + nodes.machine = + { + config, + pkgs, + modulesPath, + ... + }: + { + imports = [ + "${modulesPath}/image/repart.nix" + "${modulesPath}/profiles/bashless.nix" + ]; + + # Backdoor uses bash + testing.backdoor = false; + + virtualisation = { + directBoot.enable = false; + mountHostNixStore = false; + useEFIBoot = true; + }; + + boot.loader.grub.enable = false; + + virtualisation.fileSystems = lib.mkForce { + "/" = { + fsType = "tmpfs"; + options = [ "mode=0755" ]; + }; + + "/nix/store" = { + device = "/dev/disk/by-partlabel/${storePartitionLabel}"; + fsType = "erofs"; # Saves ~250MiB over ext4 + }; + }; + + image.repart = { + name = "bashless-image"; + mkfsOptions = { + erofs = [ "-z lz4" ]; # Saves ~150MiB over no compression + }; + partitions = { + "esp" = { + contents = { + "/EFI/BOOT/BOOT${lib.toUpper config.nixpkgs.hostPlatform.efiArch}.EFI".source = + "${config.system.build.uki}/${config.system.boot.loader.ukiFile}"; + }; + repartConfig = { + Type = "esp"; + Format = "vfat"; + SizeMinBytes = if config.nixpkgs.hostPlatform.isx86_64 then "64M" else "96M"; + }; + }; + "root" = { + storePaths = [ config.system.build.toplevel ]; + nixStorePrefix = "/"; + repartConfig = { + Type = "linux-generic"; + Format = config.fileSystems."/nix/store".fsType; + Label = storePartitionLabel; + Minimize = "best"; + }; + }; + }; + }; + }; + + testScript = + { nodes, ... }: + '' + import os + import subprocess + import tempfile + + tmp_disk_image = tempfile.NamedTemporaryFile() + + subprocess.run([ + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", + "create", + "-f", + "qcow2", + "-b", + "${nodes.machine.system.build.image}/${nodes.machine.image.fileName}", + "-F", + "raw", + tmp_disk_image.name, + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + + machine.start() + machine.wait_for_console_text("Startup finished.") + ''; +} diff --git a/nixos/tests/activation/bashless.nix b/nixos/tests/activation/bashless.nix new file mode 100644 index 000000000000..06028a81db30 --- /dev/null +++ b/nixos/tests/activation/bashless.nix @@ -0,0 +1,50 @@ +# This is a smoke test that tests that basic functionality is still available +# with the bashless profile. For this, however, we have to re-enable bash. + +{ lib, ... }: + +{ + + name = "activation-bashless"; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + + nodes.machine = + { pkgs, modulesPath, ... }: + { + imports = [ "${modulesPath}/profiles/bashless.nix" ]; + + # Forcibly re-set options that would normally be set by the bashless + # profile but that we have to re-enable to make the test instrumentation + # work. + environment.binsh = lib.mkForce null; + boot.initrd.systemd.shell.enable = false; + + # This ensures that we only have the store paths of our closure in the + # in the guest. This is necessary so we can grep in the store. + virtualisation.mountHostNixStore = false; + virtualisation.useNixStoreImage = true; + + # Re-enable just enough of a normal NixOS system to be able to run tests + programs.bash.enable = true; + environment.shell.enable = true; + environment.systemPackages = [ + pkgs.coreutils + pkgs.gnugrep + ]; + + # Unset the regex because the tests instrumentation needs bash. + system.forbiddenDependenciesRegexes = lib.mkForce [ ]; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + with subtest("/bin/sh doesn't exist"): + machine.fail("stat /bin/sh") + + bash_store_paths = machine.succeed("ls /nix/store | grep bash || true") + print(bash_store_paths) + ''; + +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index e471baaf7849..60602dde2415 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -186,6 +186,9 @@ in acme = import ./acme/default.nix { inherit runTest; }; acme-dns = runTest ./acme-dns.nix; activation = pkgs.callPackage ../modules/system/activation/test.nix { }; + activation-bashless = runTest ./activation/bashless.nix; + activation-bashless-closure = pkgs.callPackage ./activation/bashless-closure.nix { }; + activation-bashless-image = runTest ./activation/bashless-image.nix; activation-etc-overlay-immutable = runTest ./activation/etc-overlay-immutable.nix; activation-etc-overlay-mutable = runTest ./activation/etc-overlay-mutable.nix; activation-lib = pkgs.callPackage ../modules/system/activation/lib/test.nix { }; From d812db8727ebc18def56e7cb7322cc6727acdbd8 Mon Sep 17 00:00:00 2001 From: Gaetan Lepage Date: Wed, 8 Oct 2025 22:34:28 +0000 Subject: [PATCH 09/13] ruff: 0.13.1 -> 0.14.0 Diff: https://github.com/astral-sh/ruff/compare/0.13.1...0.14.0 Changelog: https://github.com/astral-sh/ruff/releases/tag/0.14.0 --- pkgs/by-name/ru/ruff/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ru/ruff/package.nix b/pkgs/by-name/ru/ruff/package.nix index 46cc44236a8f..91f01c964b9b 100644 --- a/pkgs/by-name/ru/ruff/package.nix +++ b/pkgs/by-name/ru/ruff/package.nix @@ -16,18 +16,18 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "ruff"; - version = "0.13.1"; + version = "0.14.0"; src = fetchFromGitHub { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-dCxCpJLG2qjfrMxDJOL4rCwdVYfrz3P+4kDQ9d9Mbus="; + hash = "sha256-Vueccz5lkUTdgqqZl/+PB0kVYezvPKIVxee8EJYcz4g="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-WtRryq8bmKfL3EL2kRFFokmG2f0lnS6zRMbUzGeYLDM="; + cargoHash = "sha256-bnSTiQzlZrS2tqQiRHr6gNDq40Naqxrxcyc8zcVlt7A="; nativeBuildInputs = [ installShellFiles ]; From 42768554f88cb0e50d16fdc0f72d8cd0e6abf416 Mon Sep 17 00:00:00 2001 From: K900 Date: Sun, 12 Oct 2025 14:31:11 +0300 Subject: [PATCH 10/13] linux_6_17: 6.17.1 -> 6.17.2 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 8bd7fe2a01cf..8f5b8c9e278c 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -40,8 +40,8 @@ "lts": false }, "6.17": { - "version": "6.17.1", - "hash": "sha256:182d9xf7j0n4cn61rsqs87al9xc83rawri8p3yk246a984zvwgd5", + "version": "6.17.2", + "hash": "sha256:0zzmcjkmxmcn45y20g8hz7dxs355rmczp9k8vjwc3xb5a03cpszx", "lts": false } } From e114e22e45e9a8c9c6720675aa1108d4f5186cae Mon Sep 17 00:00:00 2001 From: K900 Date: Sun, 12 Oct 2025 14:31:46 +0300 Subject: [PATCH 11/13] linux_6_16: 6.16.11 -> 6.16.12 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 8f5b8c9e278c..38342052710e 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -35,8 +35,8 @@ "lts": true }, "6.16": { - "version": "6.16.11", - "hash": "sha256:0yxsinhly689327jbvwm2nfr6cx7ynj9sd87a9var1rx8l64yc2z", + "version": "6.16.12", + "hash": "sha256:0vm257d76hmimnac8hzg66gd1mdg330sai39lywfn4m9bjydx93w", "lts": false }, "6.17": { From fca8041221aadd5d10d512c0bc0e2d974f7845d1 Mon Sep 17 00:00:00 2001 From: K900 Date: Sun, 12 Oct 2025 14:31:49 +0300 Subject: [PATCH 12/13] linux_6_12: 6.12.51 -> 6.12.52 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 38342052710e..9e9f88b39dce 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -30,8 +30,8 @@ "lts": true }, "6.12": { - "version": "6.12.51", - "hash": "sha256:08bj3b6a6jwvrpjl5sxvmzwwnc00clq98rjwb61fznd7khaasm9d", + "version": "6.12.52", + "hash": "sha256:1ccyd9h9i3xia1gqq0mggis5yv04c9ys44xp707wfcm0f3v0r1dl", "lts": true }, "6.16": { From 782505f3aa54b9cef55eca7b16dc44f3400a1669 Mon Sep 17 00:00:00 2001 From: K900 Date: Sun, 12 Oct 2025 14:31:52 +0300 Subject: [PATCH 13/13] linux_6_6: 6.6.110 -> 6.6.111 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 9e9f88b39dce..ba339f14a98c 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -25,8 +25,8 @@ "lts": true }, "6.6": { - "version": "6.6.110", - "hash": "sha256:07gv37ralrhf709plqj1hzk1adwilh6znmay6agpbf23anphvwhv", + "version": "6.6.111", + "hash": "sha256:1is6nrm5x54bw8zn8l5akp8ign185i19biks442yynfn63hp1i04", "lts": true }, "6.12": {