diff --git a/doc/release-notes/rl-2611.section.md b/doc/release-notes/rl-2611.section.md index 27bff446bb6e..c3d9415e5459 100644 --- a/doc/release-notes/rl-2611.section.md +++ b/doc/release-notes/rl-2611.section.md @@ -93,3 +93,4 @@ ### Additions and Improvements {#sec-nixpkgs-release-26.11-lib-additions-improvements} - Create the first release note entry in this section! + diff --git a/nixos/doc/manual/release-notes/rl-2611.section.md b/nixos/doc/manual/release-notes/rl-2611.section.md index 314044b1b2c6..b06f3e9cdfaf 100644 --- a/nixos/doc/manual/release-notes/rl-2611.section.md +++ b/nixos/doc/manual/release-notes/rl-2611.section.md @@ -64,6 +64,8 @@ - `security.polkit.settings` added for RFC42 style configuration of the polkitd daemon. +- The `programs.fuse` module, which provides the `fusermount3` executable and the `/etc/fuse.conf` config file, is now opt-in. The obligation to enable it has been shifted to its various consumers (e.g. gvfs, flatpak, appimage, sshfs). This can break fuse consumers at runtime, that don't explicitly declare that dependency with a module, e.g the mounting functionality in various backup tools (borg, restic, rclone, ...). + - `services.plausible` can now again seed an initial admin user declaratively via [`services.plausible.adminUser.email`](#opt-services.plausible.adminUser.email). This makes fully declarative deployments safer: Otherwise the user needed to either accept Plausible's unauthenticated "first launch" setup wizard, which lets anyone reaching the instance create the first admin account, or do more work (deploying with NixOS's default binding to `localhost` without exposing it publicly, going through the wizard, and then deploying Plausible exposed to the Internet). This option was previously removed with NixOS 25.05 due to an upstream Plausible change making declarative admin creation more difficult, but this change re-implements the admin creation directly. diff --git a/nixos/lib/test-driver/src/test_driver/machine/__init__.py b/nixos/lib/test-driver/src/test_driver/machine/__init__.py index db96445af912..00876b0e7d1b 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -1044,12 +1044,16 @@ class QemuMachine(BaseMachine): assert self.shell tic = time.time() - # TODO: do we want to bail after a set number of attempts? - while not shell_ready(timeout_secs=30): + + for _ in range(10): + if shell_ready(timeout_secs=30): + break self.log("Guest root shell did not produce any data yet...") self.log( " To debug, enter the VM and run 'systemctl status backdoor.service'." ) + else: + raise RuntimeError("Shell did not start in time") while True: chunk = self.shell.recv(1024) @@ -1587,7 +1591,7 @@ class NspawnMachine(BaseMachine): # NOTE If the test calls switch-to-configuration (with a differently configured specialization) # this will use the /etc/profile of the new specialisation while `QemuMachine` nodes # will continue to use the original /etc/profile. - command = f"set -eo pipefail; source /etc/profile; set -u; {command}" + command = f"set -eo pipefail; USER=root HOME=/root source /etc/profile; set -u; {command}" cp = subprocess.run( [ diff --git a/nixos/lib/testing/network.nix b/nixos/lib/testing/network.nix index 570b854d8e28..86d65faf21ad 100644 --- a/nixos/lib/testing/network.nix +++ b/nixos/lib/testing/network.nix @@ -135,8 +135,13 @@ let ); udevRules = map ( interface: - # MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive. - ''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower (qemu-common.qemuNicMac interface.vlan config.virtualisation.test.nodeNumber)}",NAME="${interface.name}"'' + lib.concatStringsSep ", " [ + ''SUBSYSTEM=="net"'' + ''ACTION=="add"'' + # MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive. + ''ATTR{address}=="${toLower (qemu-common.qemuNicMac interface.vlan config.virtualisation.test.nodeNumber)}"'' + ''NAME="${interface.name}"'' + ] ) interfaces; in { diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix index df4165fbbec5..05f99aff70f4 100644 --- a/nixos/modules/config/sysctl.nix +++ b/nixos/modules/config/sysctl.nix @@ -74,7 +74,9 @@ in } ( '' + set +e mmap_rnd_bits_max=$(grep "^CONFIG_ARCH_MMAP_RND_BITS_MAX=" $configfile | grep --only-matching "[0-9]*$") + set -e if [[ -z "$mmap_rnd_bits_max" ]]; then echo "Unable to determine mmap_rnd_bits_max. Check your kernel configfile is valid." exit 1 @@ -83,7 +85,9 @@ in '' # HAVE_ARCH_MMAP_RND_COMPAT_BITS is not defined on 32-bit architectures or LoongArch64 + lib.optionalString (with pkgs.stdenv.hostPlatform; (!is32bit && !isLoongArch64)) '' + set +e mmap_rnd_compat_bits_max=$(grep "^CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=" $configfile | grep --only-matching "[0-9]*$") + set -e if [[ -z "$mmap_rnd_compat_bits_max" ]]; then echo "Unable to determine mmap_rnd_compat_bits_max. Check your kernel configfile is valid." exit 1 diff --git a/nixos/modules/profiles/bashless.nix b/nixos/modules/profiles/bashless.nix index 501563b97b3d..20f2f7256f07 100644 --- a/nixos/modules/profiles/bashless.nix +++ b/nixos/modules/profiles/bashless.nix @@ -15,8 +15,6 @@ 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; diff --git a/nixos/modules/programs/appimage.nix b/nixos/modules/programs/appimage.nix index c0379557c97a..4662a6c3d8cc 100644 --- a/nixos/modules/programs/appimage.nix +++ b/nixos/modules/programs/appimage.nix @@ -43,6 +43,8 @@ in } ); environment.systemPackages = [ cfg.package ]; + + programs.fuse.enable = true; }; meta.maintainers = with lib.maintainers; [ diff --git a/nixos/modules/programs/fuse.nix b/nixos/modules/programs/fuse.nix index e7e317d7ef9b..07026cbd33db 100644 --- a/nixos/modules/programs/fuse.nix +++ b/nixos/modules/programs/fuse.nix @@ -12,9 +12,7 @@ in meta.maintainers = [ ]; options.programs.fuse = { - enable = lib.mkEnableOption "fuse" // { - default = true; - }; + enable = lib.mkEnableOption "fuse"; mountMax = lib.mkOption { # In the C code it's an "int" (i.e. signed and at least 16 bit), but diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix index 0813b1b6b84c..f6106d3e83fe 100644 --- a/nixos/modules/services/desktop-managers/plasma6.nix +++ b/nixos/modules/services/desktop-managers/plasma6.nix @@ -274,6 +274,7 @@ in services.power-profiles-daemon.enable = mkDefault true; services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true); + programs.fuse.enable = true; services.udisks2.enable = true; services.upower.enable = config.powerManagement.enable; services.libinput.enable = mkDefault true; diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix index 5756738c9fda..e874cbac7a4f 100644 --- a/nixos/modules/services/desktops/flatpak.nix +++ b/nixos/modules/services/desktops/flatpak.nix @@ -40,6 +40,8 @@ in pkgs.fuse3 ]; + programs.fuse.enable = true; + security.polkit.enable = true; fonts.fontDir.enable = true; diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix index 004810327798..cebfb0820229 100644 --- a/nixos/modules/services/desktops/gvfs.nix +++ b/nixos/modules/services/desktops/gvfs.nix @@ -40,6 +40,8 @@ in environment.systemPackages = [ cfg.package ]; + programs.fuse.enable = true; + services.dbus.packages = [ cfg.package ]; systemd.packages = [ cfg.package ]; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix index 3390374172c4..935ebff7e5bd 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix @@ -17,7 +17,7 @@ in type = types.str; default = "http://localhost:8053/"; description = '' - HTTP XML API address of an Bind server. + HTTP API address of a BIND server. ''; }; bindTimeout = mkOption { @@ -29,13 +29,14 @@ in }; bindVersion = mkOption { type = types.enum [ - "xml.v2" + "json" + "xml" "xml.v3" "auto" ]; - default = "auto"; + default = "json"; description = '' - BIND statistics version. Can be detected automatically. + BIND statistics version. Defaults to JSON. ''; }; bindGroups = mkOption { diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index 3c75423f3ffa..4e676b80a1aa 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -332,8 +332,9 @@ in boot.kernel.sysctl."net.core.rmem_max" = lib.mkDefault 7500000; boot.kernel.sysctl."net.core.wmem_max" = lib.mkDefault 7500000; - programs.fuse = lib.mkIf (cfg.autoMount && cfg.settings.Mounts.FuseAllowOther) { - userAllowOther = true; + programs.fuse = { + enable = lib.mkIf cfg.autoMount true; + userAllowOther = lib.mkIf cfg.settings.Mounts.fuseAllowOther true; }; users.users = lib.mkIf (cfg.user == "ipfs") { diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index f242cb8c6a5b..311bf9cc9aa1 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -124,6 +124,16 @@ let jobScripts = concatLists ( mapAttrsToList (_: unit: unit.jobScripts or [ ]) (filterAttrs (_: v: v.enable) cfg.services) ); + unitEnv = pkgs.buildEnv { + name = "initrd-unit-env"; + paths = concatLists ( + mapAttrsToList (_: unit: unit.path or [ ]) (filterAttrs (_: v: v.enable) cfg.services) + ); + pathsToLink = [ + "/bin" + "/sbin" + ]; + }; stage1Units = generateUnits { type = "initrd"; @@ -636,6 +646,7 @@ in "${pkgs.bashNonInteractive}/bin" ] ++ jobScripts + ++ [ unitEnv ] ++ map (c: removeAttrs c [ "text" ]) (builtins.attrValues cfg.contents) ++ lib.optional (pkgs.stdenv.hostPlatform.libc == "glibc") "${pkgs.glibc}/lib/libnss_files.so.2"; @@ -763,6 +774,7 @@ in ]; }; serviceConfig.Type = "oneshot"; + serviceConfig.EnvironmentFile = "-/etc/switch-root.conf"; description = "NixOS Activation"; script = # bash @@ -770,6 +782,14 @@ in set -uo pipefail export PATH="/bin:${cfg.package.util-linux}/bin" + # A non-NixOS closure (e.g. init=/bin/sh) has no prepare-root; + # initrd-find-nixos-closure records this as a non-empty NEW_INIT. + # Skip activation and let initrd-switch-root hand over to it directly. + if [ -n "''${NEW_INIT:-}" ]; then + echo "$NEW_INIT is not a NixOS system - not activating" + exit 0 + fi + closure="$(realpath /nixos-closure)" # Initialize the system diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix index b00c25e37802..31842fc9c0c6 100644 --- a/nixos/modules/system/etc/etc-activation.nix +++ b/nixos/modules/system/etc/etc-activation.nix @@ -71,6 +71,10 @@ RequiresMountsFor = [ "/sysroot/nix/store" ]; + # find-etc only creates this symlink for a NixOS init. For a + # non-NixOS init= (e.g. init=/bin/sh) it is absent, so skip the + # mount instead of failing the whole initrd. + ConditionPathExists = "/etc-metadata-image"; }; requires = [ config.boot.initrd.systemd.services.initrd-find-etc.name @@ -123,6 +127,8 @@ "/run/nixos-etc-metadata" ]; DefaultDependencies = false; + # Skip for a non-NixOS init=, see the metadata mount above. + ConditionPathExists = "/etc-basedir"; }; } ]; @@ -140,6 +146,8 @@ # before the overlay is mounted. "/run/nixos-etc-metadata" ]; + # Skip for a non-NixOS init=, see the metadata mount above. + ConditionPathExists = "/etc-metadata-image"; }; serviceConfig = { Type = "oneshot"; diff --git a/nixos/modules/tasks/filesystems/sshfs.nix b/nixos/modules/tasks/filesystems/sshfs.nix index f070779ecc54..917ceea7b691 100644 --- a/nixos/modules/tasks/filesystems/sshfs.nix +++ b/nixos/modules/tasks/filesystems/sshfs.nix @@ -10,6 +10,8 @@ lib.mkIf (config.boot.supportedFilesystems.sshfs or config.boot.supportedFilesystems."fuse.sshfs" or false) { + programs.fuse.enable = true; + system.fsPackages = [ pkgs.sshfs ]; }; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 09b0a4632d28..d6175df3d920 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1646,6 +1646,7 @@ in "i686-linux" ] ./initrd-network-openvpn { systemdStage1 = true; }; systemd-initrd-networkd-ssh = runTest ./systemd-initrd-networkd-ssh.nix; + systemd-initrd-non-nixos = runTest ./systemd-initrd-non-nixos.nix; systemd-initrd-shutdown = runTest { imports = [ ./systemd-shutdown.nix ]; _module.args.systemdStage1 = true; diff --git a/nixos/tests/gocryptfs.nix b/nixos/tests/gocryptfs.nix index d0e5df5e40b5..763e1c1407fa 100644 --- a/nixos/tests/gocryptfs.nix +++ b/nixos/tests/gocryptfs.nix @@ -13,6 +13,8 @@ pkgs.openssl ]; + programs.fuse.enable = true; + specialisation.fstab-test.configuration = { # This can't be fileSytems, as the qemu machinery doesn't honor it. virtualisation.fileSystems."/plain" = { diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index b1033987565a..990dc02a5ab8 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -111,7 +111,7 @@ let wait_for_unit("prometheus-bind-exporter.service") wait_for_open_port(9119) succeed( - "curl -sSf http://localhost:9119/metrics | grep 'bind_query_recursions_total 0'" + "curl -sSf http://localhost:9119/metrics | grep 'bind_up 1'" ) ''; }; diff --git a/nixos/tests/simple-container.nix b/nixos/tests/simple-container.nix index 269853423651..5cd3478f3d40 100644 --- a/nixos/tests/simple-container.nix +++ b/nixos/tests/simple-container.nix @@ -1,11 +1,16 @@ { name = "simple-container"; - containers.machine = { }; + containers = { + machine = { pkgs, ... }: { + users.users.root.packages = [ pkgs.hello ]; + }; + noprofile = { }; + }; testScript = '' start_all() - machine.wait_for_unit("multi-user.target") - machine.shutdown() + machine.succeed("hello") + noprofile.fail("hello") ''; } diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 2dbc9386f8ae..b81d84086f2e 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -43,6 +43,19 @@ let server.serve_forever() ''; + # Per-connection (Accept=yes) socket-activated service that requires the + # connection socket to be passed via socket activation and fails when started + # without one. It greets the client and stays alive for as long as the + # connection is held open. + acceptSocketTest = pkgs.writeShellScript "accept-socket-test.sh" '' + if [ "''${LISTEN_FDS:-0}" -lt 1 ]; then + echo "Expected exactly one socket, got 0" >&2 + exit 4 + fi + printf hello >&3 + exec ${lib.getExe' pkgs.coreutils "cat"} <&3 >/dev/null + ''; + in { name = "switch-test"; @@ -508,6 +521,32 @@ in }; }; + accept-socket.configuration = { + systemd.sockets.accept-socket = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ "/run/accept-test.sock" ]; + socketConfig = { + Accept = "yes"; + SocketMode = "0777"; + }; + }; + systemd.services."accept-socket@" = { + description = "A per-connection socket-activated service"; + serviceConfig.ExecStart = acceptSocketTest; + }; + }; + + accept-socket-service-modified.configuration = { + imports = [ accept-socket.configuration ]; + systemd.services."accept-socket@".serviceConfig.X-Test = "test"; + }; + + socket-activated-without-socket.configuration = { + imports = [ simple-socket.configuration ]; + systemd.sockets.socket-activated.enable = false; + systemd.services.socket-activated.wantedBy = [ "multi-user.target" ]; + }; + mount.configuration = { systemd.mounts = [ { @@ -1587,6 +1626,49 @@ in if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello": raise Exception("Socket was not properly activated after the service was restarted") + # A service transitioning to socket activation is not started directly, + # it's left for the newly started socket to activate on demand + switch_to_specialisation("${machine}", "socket-activated-without-socket") + machine.succeed("systemctl is-active socket-activated.service") + out = switch_to_specialisation("${machine}", "simple-socket-stop-if-changed") + assert_contains(out, "stopping the following units: socket-activated.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_contains(out, "the following new units were started: socket-activated.socket\n") + machine.succeed("[ -S /run/test.sock ]") + if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello": + raise Exception("Socket was not properly activated after the transition") + + with subtest("socket-activated services with Accept=yes"): + # Socket-activated services don't get started, just the socket + machine.fail("[ -S /run/accept-test.sock ]") + out = switch_to_specialisation("${machine}", "accept-socket") + assert_contains(out, "the following new units were started: accept-socket.socket\n") + machine.succeed("[ -S /run/accept-test.sock ]") + + # Hold a connection open so a per-connection instance keeps running + machine.succeed("socat EXEC:'sleep infinity' UNIX-CONNECT:/run/accept-test.sock >&2 &") + instance = machine.wait_until_succeeds( + "systemctl list-units --no-legend --state=running 'accept-socket@*.service' " + + "| grep -m1 -o 'accept-socket@[^ ]*\\.service'" + ).strip() + + # Changing the templated service must stop the running instance and + # restart the socket instead of (re)starting the per-connection + # instance, which cannot be started without a connection socket + out = switch_to_specialisation("${machine}", "accept-socket-service-modified") + assert_contains(out, "stopping the following units:") + assert_contains(out, instance) + assert_contains(out, "accept-socket.socket") + assert_contains(out, "\nstarting the following units: accept-socket.socket\n") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "\nrestarting the following units:") + # The per-connection instance must not be (re)started + starting = out[out.index("\nstarting the following units:") :] + assert instance not in starting, f"instance {instance} should not be (re)started" + # Socket-activation of the unit still works + if machine.succeed("socat - UNIX-CONNECT:/run/accept-test.sock /dev/console + exec ${pkgs.coreutils}/bin/sleep infinity + ''; + + common = { + boot.initrd.systemd.enable = true; + + virtualisation = { + # tmpfs root, like real non-NixOS closure init= microvm consumers. + diskImage = null; + + graphics = false; + }; + + # Speed up wait_for_console. + boot.consoleLogLevel = lib.mkForce 3; + boot.initrd.systemd.managerEnvironment.SYSTEMD_LOG_LEVEL = "warning"; + + # switch-root needs an os-release on the target root. A real system has one + # on disk; our fresh tmpfs does not, so create it. + boot.initrd.systemd.tmpfiles.settings."10-os-release"."/sysroot/etc/os-release".f = { + mode = "0644"; + argument = "ID=test-non-nixos"; + }; + }; +in +{ + name = "systemd-initrd-non-nixos"; + + nodes = { + bashActivation = common; + + nixosInit = { + imports = [ common ]; + system.nixos-init.enable = true; + system.etc.overlay.enable = true; + services.userborn.enable = true; + }; + }; + + testScript = '' + import os + + # The last init= on the cmdline wins; QEMU_KERNEL_PARAMS is appended after + # the default one, so this boots our non-NixOS init. + os.environ["QEMU_KERNEL_PARAMS"] = "init=${lib.getExe nonNixosInit}" + + start_all() + + # If a code path does not skip the non-NixOS init, switch-root is blocked and + # the machine drops to emergency mode: the marker never appears and the wait + # times out. + with subtest("bash initrd-nixos-activation skips a non-NixOS init"): + bashActivation.wait_for_console_text("${marker}", timeout=300) + + with subtest("nixos-init switches to a non-NixOS init directly"): + nixosInit.wait_for_console_text("${marker}", timeout=300) + ''; +} diff --git a/pkgs/by-name/bi/bind/package.nix b/pkgs/by-name/bi/bind/package.nix index 2f12ebf30ea0..1da6cba11479 100644 --- a/pkgs/by-name/bi/bind/package.nix +++ b/pkgs/by-name/bi/bind/package.nix @@ -9,6 +9,7 @@ libidn2, libtool, libxml2, + json_c, openssl, liburcu, libuv, @@ -29,11 +30,11 @@ stdenv.mkDerivation (finalAttrs: { pname = "bind"; - version = "9.20.23"; + version = "9.20.24"; src = fetchurl { url = "https://downloads.isc.org/isc/bind9/${finalAttrs.version}/bind-${finalAttrs.version}.tar.xz"; - hash = "sha256-XUR1rtP55QDvVUsrFNlyvbg9M94hSps76SkY6kaQg3E="; + hash = "sha256-mJ/vH8iOpZ0EzYb4VNylpGFqIKmWi83ePBo2aKs2vgg="; }; outputs = [ @@ -59,6 +60,7 @@ stdenv.mkDerivation (finalAttrs: { libidn2 libtool libxml2 + json_c openssl liburcu libuv diff --git a/pkgs/by-name/gr/gram/package.nix b/pkgs/by-name/gr/gram/package.nix index 6d6f84306f33..48a4664be6fd 100644 --- a/pkgs/by-name/gr/gram/package.nix +++ b/pkgs/by-name/gr/gram/package.nix @@ -31,7 +31,7 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "gram"; - version = "2.1.2"; + version = "2.2.0"; outputs = [ "out" @@ -44,7 +44,7 @@ rustPlatform.buildRustPackage (finalAttrs: { owner = "GramEditor"; repo = "gram"; tag = finalAttrs.version; - hash = "sha256-7FzAvC/JMMIFcuTGkL2Ju644UAIsneOMhiDUFnQske4="; + hash = "sha256-w0uZ2qAc3Tt6QVRAX97LWW9aOs02fG1SEYCDhpUhinE="; }; postPatch = '' @@ -54,7 +54,7 @@ rustPlatform.buildRustPackage (finalAttrs: { --replace-fail '$CARGO_ABOUT_VERSION' '${cargo-about.version}' ''; - cargoHash = "sha256-feESY8ALSG3xa906HBc4pOKGerQ1jF7VUxzvUcsZbrY="; + cargoHash = "sha256-+lmDbawAIRllC7LzGJ9qPMtHXPd5aMoul47YOA7nfXA="; __structuredAttrs = true; @@ -71,7 +71,7 @@ rustPlatform.buildRustPackage (finalAttrs: { dontUseCmakeConfigure = true; buildInputs = [ - libgit2 + (libgit2.override { withExperimentalSha256 = true; }) openssl sqlite zlib diff --git a/pkgs/by-name/li/libgit2/package.nix b/pkgs/by-name/li/libgit2/package.nix index 568fa09201aa..c17240378a92 100644 --- a/pkgs/by-name/li/libgit2/package.nix +++ b/pkgs/by-name/li/libgit2/package.nix @@ -17,6 +17,7 @@ gitstatus, llhttp, withGssapi ? false, + withExperimentalSha256 ? false, krb5, }: @@ -43,6 +44,7 @@ stdenv.mkDerivation (finalAttrs: { "-DUSE_HTTP_PARSER=llhttp" "-DUSE_SSH=ON" (lib.cmakeBool "USE_GSSAPI" withGssapi) + (lib.cmakeBool "EXPERIMENTAL_SHA256" withExperimentalSha256) "-DBUILD_SHARED_LIBS=${if staticBuild then "OFF" else "ON"}" ] ++ lib.optionals stdenv.hostPlatform.isWindows [ @@ -89,6 +91,13 @@ stdenv.mkDerivation (finalAttrs: { ) ''; + postInstall = lib.optionalString withExperimentalSha256 '' + # Downstream Rust bindings (git2-rs / git2-sys) expect experimental headers + # to be located at 'git2/experimental.h', but upstream libgit2 installs them + # into 'git2-experimental/' when EXPERIMENTAL_SHA256 is enabled. + ln -s git2-experimental $dev/include/git2 + ''; + passthru.tests = lib.mapAttrs (_: v: v.override { libgit2 = finalAttrs.finalPackage; }) { inherit libgit2-glib; inherit (python3Packages) pygit2; diff --git a/pkgs/by-name/md/mdadm4/no-self-references.patch b/pkgs/by-name/md/mdadm4/no-self-references.patch deleted file mode 100644 index 3b3dc4d84609..000000000000 --- a/pkgs/by-name/md/mdadm4/no-self-references.patch +++ /dev/null @@ -1,124 +0,0 @@ -diff --git a/Makefile b/Makefile -index 2a51d813..a31ac48a 100644 ---- a/Makefile -+++ b/Makefile -@@ -63,6 +63,9 @@ endif - ifdef DEBIAN - CPPFLAGS += -DDEBIAN - endif -+ifdef NIXOS -+CPPFLAGS += -DNIXOS -+endif - ifdef DEFAULT_OLD_METADATA - CPPFLAGS += -DDEFAULT_OLD_METADATA - DEFAULT_METADATA=0.90 -@@ -129,6 +132,7 @@ endif - INSTALL = /usr/bin/install - DESTDIR = - BINDIR = /sbin -+INSTALL_BINDIR = ${BINDIR} - MANDIR = /usr/share/man - MAN4DIR = $(MANDIR)/man4 - MAN5DIR = $(MANDIR)/man5 -@@ -253,16 +257,16 @@ sha1.o : sha1.c sha1.h md5.h - install : install-bin install-man install-udev - - install-static : mdadm.static install-man -- $(INSTALL) -D $(STRIP) -m 755 mdadm.static $(DESTDIR)$(BINDIR)/mdadm -+ $(INSTALL) -D $(STRIP) -m 755 mdadm.static $(DESTDIR)$(INSTALL_BINDIR)/mdadm - - install-tcc : mdadm.tcc install-man -- $(INSTALL) -D $(STRIP) -m 755 mdadm.tcc $(DESTDIR)$(BINDIR)/mdadm -+ $(INSTALL) -D $(STRIP) -m 755 mdadm.tcc $(DESTDIR)$(INSTALL_BINDIR)/mdadm - - install-uclibc : mdadm.uclibc install-man -- $(INSTALL) -D $(STRIP) -m 755 mdadm.uclibc $(DESTDIR)$(BINDIR)/mdadm -+ $(INSTALL) -D $(STRIP) -m 755 mdadm.uclibc $(DESTDIR)$(INSTALL_BINDIR)/mdadm - - install-klibc : mdadm.klibc install-man -- $(INSTALL) -D $(STRIP) -m 755 mdadm.klibc $(DESTDIR)$(BINDIR)/mdadm -+ $(INSTALL) -D $(STRIP) -m 755 mdadm.klibc $(DESTDIR)$(INSTALL_BINDIR)/mdadm - - install-man: mdadm.8 md.4 mdadm.conf.5 mdmon.8 - $(INSTALL) -D -m 644 mdadm.8 $(DESTDIR)$(MAN8DIR)/mdadm.8 -@@ -305,7 +309,7 @@ install-bin: mdadm mdmon - $(INSTALL) -D $(STRIP) -m 755 mdmon $(DESTDIR)$(BINDIR)/mdmon - - uninstall: -- rm -f $(DESTDIR)$(MAN8DIR)/mdadm.8 $(DESTDIR)$(MAN8DIR)/mdmon.8 $(DESTDIR)$(MAN4DIR)/md.4 $(DESTDIR)$(MAN5DIR)/mdadm.conf.5 $(DESTDIR)$(BINDIR)/mdadm -+ rm -f $(DESTDIR)$(MAN8DIR)/mdadm.8 $(DESTDIR)$(MAN8DIR)/mdmon.8 $(DESTDIR)$(MAN4DIR)/md.4 $(DESTDIR)$(MAN5DIR)/mdadm.conf.5 $(DESTDIR)$(INSTALL_BINDIR)/mdadm - - test: mdadm mdmon test_stripe swap_super raid6check - @echo "Please run './test' as root" -diff --git a/policy.c b/policy.c -index eee9ef63..9f916e9d 100644 ---- a/policy.c -+++ b/policy.c -@@ -817,12 +817,39 @@ char *find_rule(struct rule *rule, char *rule_type) - #define UDEV_RULE_FORMAT \ - "ACTION==\"add\", SUBSYSTEM==\"block\", " \ - "ENV{DEVTYPE}==\"%s\", ENV{ID_PATH}==\"%s\", " \ --"RUN+=\"" BINDIR "/mdadm --incremental $env{DEVNAME}\"\n" -+"RUN+=\"%s/mdadm --incremental $env{DEVNAME}\"\n" - - #define UDEV_RULE_FORMAT_NOTYPE \ - "ACTION==\"add\", SUBSYSTEM==\"block\", " \ - "ENV{ID_PATH}==\"%s\", " \ --"RUN+=\"" BINDIR "/mdadm --incremental $env{DEVNAME}\"\n" -+"RUN+=\"%s/mdadm --incremental $env{DEVNAME}\"\n" -+ -+#ifdef NIXOS -+const char *get_mdadm_bindir(void) -+{ -+ static char *bindir = NULL; -+ if (bindir != NULL) { -+ return bindir; -+ } else { -+ int len; -+ bindir = xmalloc(1025); -+ len = readlink("/proc/self/exe", bindir, 1024); -+ if (len > 0) { -+ char *basename; -+ if ((basename = strrchr(bindir, '/')) != NULL) -+ *basename = '\0'; -+ else -+ *(bindir + len) = '\0'; -+ } else { -+ *bindir = '\0'; -+ } -+ return bindir; -+ } -+} -+#define SELF get_mdadm_bindir() -+#else -+#define SELF BINDIR -+#endif - - /* Write rule in the rule file. Use format from UDEV_RULE_FORMAT */ - int write_rule(struct rule *rule, int fd, int force_part) -@@ -836,9 +863,9 @@ int write_rule(struct rule *rule, int fd, int force_part) - if (force_part) - typ = type_part; - if (typ) -- snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT, typ, pth); -+ snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT, typ, pth, SELF); - else -- snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT_NOTYPE, pth); -+ snprintf(line, sizeof(line) - 1, UDEV_RULE_FORMAT_NOTYPE, pth, SELF); - return write(fd, line, strlen(line)) == (int)strlen(line); - } - -diff --git a/util.c b/util.c -index 3d05d074..e004a798 100644 ---- a/util.c -+++ b/util.c -@@ -1913,7 +1913,9 @@ int start_mdmon(char *devnm) - char pathbuf[1024]; - char *paths[4] = { - pathbuf, -+#ifndef NIXOS - BINDIR "/mdmon", -+#endif - "./mdmon", - NULL - }; diff --git a/pkgs/by-name/md/mdadm4/package.nix b/pkgs/by-name/md/mdadm4/package.nix index 2143acc494e5..a35b504f6234 100644 --- a/pkgs/by-name/md/mdadm4/package.nix +++ b/pkgs/by-name/md/mdadm4/package.nix @@ -15,27 +15,16 @@ stdenv.mkDerivation (finalAttrs: { pname = "mdadm"; - version = "4.4"; + version = "4.6"; src = fetchgit { url = "https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git"; tag = "mdadm-${finalAttrs.version}"; - hash = "sha256-jGmc8fkJM0V9J7V7tQPXSF/WD0kzyEAloBAwaAFenS0="; + hash = "sha256-jFsVPJC4lcShkSwQCGjVdVkvk4q4weM7i5DzrLgpuSM="; }; patches = [ - ./no-self-references.patch ./fix-hardcoded-mapdir.patch - # Fixes build on musl - (fetchurl { - url = "https://raw.githubusercontent.com/void-linux/void-packages/e58d2b17d3c40faffc0d426aab00184f28d9dafa/srcpkgs/mdadm/patches/musl.patch"; - hash = "sha256-TIcQs+8RM5Q6Z8MHkI50kaJd7f9WdS/EVI16F7b2+SA="; - }) - # Fixes build on musl 1.2.5+ - (fetchurl { - url = "https://lore.kernel.org/linux-raid/20240220165158.3521874-1-raj.khem@gmail.com/raw"; - hash = "sha256-JOZ8n7zi+nq236NPpB4e2gUy8I3l3DbcoLhpeL73f98="; - }) ]; makeFlags = [ @@ -97,6 +86,7 @@ stdenv.mkDerivation (finalAttrs: { }; meta = { + changelog = "https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git/tree/CHANGELOG.md?h=${finalAttrs.src.tag}"; description = "Programs for managing RAID arrays under Linux"; homepage = "https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git"; license = lib.licenses.gpl2Plus; diff --git a/pkgs/by-name/ni/nixos-init/src/find_etc.rs b/pkgs/by-name/ni/nixos-init/src/find_etc.rs index 8b994e1d7534..d06f7bbb0114 100644 --- a/pkgs/by-name/ni/nixos-init/src/find_etc.rs +++ b/pkgs/by-name/ni/nixos-init/src/find_etc.rs @@ -3,7 +3,7 @@ use std::{os::unix, path::Path}; use anyhow::{Context, Result}; use crate::config::Config; -use crate::{SYSROOT_PATH, find_toplevel_in_prefix, resolve_in_prefix}; +use crate::{SYSROOT_PATH, find_init_in_prefix, resolve_in_prefix, verify_init_is_nixos}; /// Entrypoint for the `find-etc` binary. /// @@ -12,7 +12,20 @@ use crate::{SYSROOT_PATH, find_toplevel_in_prefix, resolve_in_prefix}; /// This avoids needing a reference to the toplevel embedded in the initrd and thus reduces the /// need to re-build it. pub fn find_etc() -> Result<()> { - let toplevel = find_toplevel_in_prefix(SYSROOT_PATH)?; + let init_in_sysroot = + find_init_in_prefix(SYSROOT_PATH).context("Failed to find init in sysroot")?; + + // A non-NixOS init= (e.g. init=/bin/sh) has no etc metadata image. Skip + // without creating the symlinks: the etc-overlay mounts are gated on them + // and so skip too, and initrd-init switches root to the init directly. + let Ok(toplevel) = verify_init_is_nixos(SYSROOT_PATH, &init_in_sysroot) else { + log::info!( + "{} is not a NixOS system - not setting up the etc overlay.", + init_in_sysroot.display() + ); + return Ok(()); + }; + let config = Config::from_toplevel(&toplevel, SYSROOT_PATH)?; let basedir = config diff --git a/pkgs/by-name/ni/nixos-init/src/lib.rs b/pkgs/by-name/ni/nixos-init/src/lib.rs index c781757c9b82..bbb4abdb829b 100644 --- a/pkgs/by-name/ni/nixos-init/src/lib.rs +++ b/pkgs/by-name/ni/nixos-init/src/lib.rs @@ -27,16 +27,6 @@ pub use crate::{ pub const SYSROOT_PATH: &str = "/sysroot"; -/// Find the path to the toplevel closure of the system in a prefix. -/// -/// Uses the `init=` parameter on the kernel command-line. -/// -/// Returns the relative path of the init to the prefix, e.g. without the `/sysroot` prefix. -pub fn find_toplevel_in_prefix(prefix: &str) -> Result { - let init_in_sysroot = find_init_in_prefix(prefix)?; - verify_init_is_nixos(prefix, init_in_sysroot) -} - /// Verify that an init path is inside a `NixOS` toplevel directory. /// /// If the path is verified, returns the path to the toplevel. @@ -87,20 +77,16 @@ pub fn find_init_in_prefix(prefix: &str) -> Result { } /// Extract the value of the `init` parameter from the given kernel `cmdline`. +/// +/// If `init=` appears multiple times the last one wins, matching the kernel. +/// This is what makes appending `init=/bin/sh` at the boot prompt work even +/// though the boot entry already has an `init=`. fn extract_init(cmdline: &str) -> Result { - let init_params: Vec<&str> = cmdline + let init = cmdline .split_ascii_whitespace() - .filter(|p| p.starts_with("init=")) - .collect(); - - if init_params.len() != 1 { - bail!("Expected exactly one init param on kernel cmdline: {cmdline}") - } - - let init = init_params - .first() - .and_then(|s| s.split('=').next_back()) - .context("Failed to extract init path from kernel cmdline: {cmdline}")?; + .filter_map(|p| p.strip_prefix("init=")) + .next_back() + .with_context(|| format!("No init= parameter on kernel cmdline: {cmdline}"))?; Ok(PathBuf::from(init)) } @@ -129,4 +115,25 @@ mod tests { Ok(()) } + + #[test] + fn test_extract_init_single() { + assert_eq!( + extract_init("root=fstab init=/nix/store/xxx-nixos/init quiet").unwrap(), + PathBuf::from("/nix/store/xxx-nixos/init") + ); + } + + #[test] + fn test_extract_init_last_wins() { + assert_eq!( + extract_init("init=/nix/store/xxx-nixos/init init=/bin/sh").unwrap(), + PathBuf::from("/bin/sh") + ); + } + + #[test] + fn test_extract_init_missing() { + assert!(extract_init("root=fstab quiet").is_err()); + } } diff --git a/pkgs/by-name/pr/prometheus-bind-exporter/package.nix b/pkgs/by-name/pr/prometheus-bind-exporter/package.nix index 1749028329e9..b135449f097b 100644 --- a/pkgs/by-name/pr/prometheus-bind-exporter/package.nix +++ b/pkgs/by-name/pr/prometheus-bind-exporter/package.nix @@ -7,16 +7,16 @@ buildGoModule rec { pname = "bind_exporter"; - version = "0.7.0"; + version = "0.8.0"; src = fetchFromGitHub { rev = "v${version}"; owner = "prometheus-community"; repo = "bind_exporter"; - sha256 = "sha256-x/XGatlXCKo9cI92JzFItApsjuZAfZX+8IZRpy7PVUo="; + sha256 = "sha256-r1P+zy3iMgPmfvIBgycW8KS0gfNOxCT9YMmHdeY4uXA="; }; - vendorHash = "sha256-f0ei/zotOj5ebURAOWUox/7J3jS2abQ5UgjninI9nRk="; + vendorHash = "sha256-/fPj5LOe3QdnVPdtYdaqtnGMJ7/SZ458mpvjwO8TxEI="; passthru.tests = { inherit (nixosTests.prometheus-exporters) bind; }; diff --git a/pkgs/by-name/ru/ruff/package.nix b/pkgs/by-name/ru/ruff/package.nix index 8aa8f51b8273..ca5d02475508 100644 --- a/pkgs/by-name/ru/ruff/package.nix +++ b/pkgs/by-name/ru/ruff/package.nix @@ -16,7 +16,7 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "ruff"; - version = "0.15.17"; + version = "0.15.18"; __structuredAttrs = true; @@ -24,12 +24,12 @@ rustPlatform.buildRustPackage (finalAttrs: { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-+UsKRBe+lp/LdsmK/W11wCt2RypEryA5eBPb01OKCJw="; + hash = "sha256-DH00tENXdCdNcGPXPGzZsU3RVYQ0VBe1QLvbgEg/G6k="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-y1sKf+KXya/K+WUiIE357U6DXh/d+AQgj0SQIi1gpUw="; + cargoHash = "sha256-RmK14NMPbhoRVLwIF5GXdGQg2AnMH20JGx7XF4HO+wY="; nativeBuildInputs = [ installShellFiles ]; diff --git a/pkgs/by-name/st/strace/package.nix b/pkgs/by-name/st/strace/package.nix index 86f8b0fa4c42..77345bd6a3ae 100644 --- a/pkgs/by-name/st/strace/package.nix +++ b/pkgs/by-name/st/strace/package.nix @@ -11,11 +11,11 @@ stdenv.mkDerivation (finalAttrs: { pname = "strace"; - version = "7.0"; + version = "7.1"; src = fetchurl { url = "https://strace.io/files/${finalAttrs.version}/strace-${finalAttrs.version}.tar.xz"; - hash = "sha256-bJJBm+Py7FYLMXKKRlIhfFmGTIZCunsbN3GxsBOtB0s="; + hash = "sha256-gXQ+zypbRBhrL1A4r9yL7aflxwrtFbT7+8xuns4kSQ8="; }; separateDebugInfo = true; diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs b/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs index 1bc5810d3fd3..db6009945e9c 100644 --- a/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs +++ b/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs @@ -743,11 +743,6 @@ fn handle_modified_unit( // This unit should be restarted instead of stopped and started. units_to_restart.insert(unit.to_string(), ()); record_unit(&restart_list, unit); - // Remove from units to reload so we don't restart and reload - if units_to_reload.contains_key(unit) { - units_to_reload.remove(unit); - unrecord_unit(&reload_list, unit); - } } else { // If this unit is socket-activated, then stop the socket unit(s) as well, and // restart the socket(s) instead of the service. @@ -769,10 +764,17 @@ fn handle_modified_unit( }; if sockets.is_empty() { - sockets.push(format!("{base_name}.socket")); + // For a templated instance (`foo@bar.service`), `base_name` + // includes the trailing `@`; the implicitly-associated socket + // is `foo.socket`, so strip it. + let socket_base = base_name.strip_suffix('@').unwrap_or(base_name); + sockets.push(format!("{socket_base}.socket")); } for socket in &sockets { + let socket_in_new_config = + toplevel.join(scope.etc_dir()).join(socket).exists(); + if active_cur.contains_key(socket) { // We can now be sure this is a socket-activated unit @@ -783,7 +785,7 @@ fn handle_modified_unit( } // Only restart sockets that actually exist in new configuration: - if toplevel.join(scope.etc_dir()).join(socket).exists() { + if socket_in_new_config { if use_restart_as_stop_and_start { units_to_restart.insert(socket.to_string(), ()); record_unit(&restart_list, socket); @@ -794,12 +796,9 @@ fn handle_modified_unit( socket_activated = true; } - - // Remove from units to reload so we don't restart and reload - if units_to_reload.contains_key(unit) { - units_to_reload.remove(unit); - unrecord_unit(&reload_list, unit); - } + } else if socket_in_new_config { + // Transitioning to socket activation; let the socket start it. + socket_activated = true; } } } @@ -828,11 +827,11 @@ fn handle_modified_unit( } else { units_to_stop.insert(unit.to_string(), ()); } - // Remove from units to reload so we don't restart and reload - if units_to_reload.contains_key(unit) { - units_to_reload.remove(unit); - unrecord_unit(&reload_list, unit); - } + } + + // Remove from units to reload so we don't restart and reload + if units_to_reload.remove(unit).is_some() { + unrecord_unit(&reload_list, unit); } } } diff --git a/pkgs/by-name/ty/ty/package.nix b/pkgs/by-name/ty/ty/package.nix index 41db756b4492..48a4dbae05f2 100644 --- a/pkgs/by-name/ty/ty/package.nix +++ b/pkgs/by-name/ty/ty/package.nix @@ -17,7 +17,7 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "ty"; - version = "0.0.49"; + version = "0.0.52"; __structuredAttrs = true; src = fetchFromGitHub { @@ -25,7 +25,7 @@ rustPlatform.buildRustPackage (finalAttrs: { repo = "ty"; tag = finalAttrs.version; fetchSubmodules = true; - hash = "sha256-IKeoskueujGYFjhUd3V7iwKwZjFZqG3OYfe36S6J2aw="; + hash = "sha256-p9fTzQ4DYvnwrtLdpSTekBV4ZbR1KETR8dfsbp8CDpo="; }; # For Darwin platforms, remove the integration test for file notifications, @@ -39,7 +39,7 @@ rustPlatform.buildRustPackage (finalAttrs: { cargoBuildFlags = [ "--package=ty" ]; - cargoHash = "sha256-y1sKf+KXya/K+WUiIE357U6DXh/d+AQgj0SQIi1gpUw="; + cargoHash = "sha256-NUIdYOeyRsR/ZQueEXshYdWTnSeQiRjZRRi2ag8Dm48="; nativeBuildInputs = [ installShellFiles ]; buildInputs = [ rust-jemalloc-sys ]; diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index d3dd8012c508..54ff5434cfe3 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.18": { - "version": "6.18.35", - "hash": "sha256:0dpjprjzc4w44kw49jcgx1ffrm6gxn2gsnsz3hhmw4hr4a9h51pp", + "version": "6.18.36", + "hash": "sha256:0kn4r43lnd5nb5c298b30030qyaxv05s7k40n9si1j3iyk4qdazv", "lts": true }, "7.0": { diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix index 98750c6c2ad2..e5f941af3770 100644 --- a/pkgs/pkgs-lib/formats.nix +++ b/pkgs/pkgs-lib/formats.nix @@ -146,14 +146,14 @@ optionalAttrs allowAliases aliases runCommand name { nativeBuildInputs = [ jq ]; - value = toJSON value; + inherit value; preferLocalBuild = true; __structuredAttrs = true; } + # NIX_ATTRS_JSON_FILE won't have `value` if it's null, but jq returns null for missing properties anyway + # jsonNull test keeps this in check '' - valuePath="$TMPDIR/value" - printf "%s" "$value" > "$valuePath" - jq . "$valuePath" > $out + jq .value "$NIX_ATTRS_JSON_FILE" > $out '' ) { }; @@ -171,14 +171,16 @@ optionalAttrs allowAliases aliases runCommand name { nativeBuildInputs = [ remarshal_0_17 ]; - value = toJSON value; + inherit value; preferLocalBuild = true; __structuredAttrs = true; } '' - valuePath="$TMPDIR/value" - printf "%s" "$value" > "$valuePath" - json2yaml "$valuePath" "$out" + json2yaml ${ + # attributes with null values are omitted from the JSON with structured attrs + # yaml_1_1Null test keeps this in check + if value == null then ''<(echo "null")'' else ''--unwrap value "$NIX_ATTRS_JSON_FILE"'' + } "$out" '' ) { }; @@ -196,14 +198,16 @@ optionalAttrs allowAliases aliases runCommand name { nativeBuildInputs = [ remarshal ]; - value = toJSON value; + inherit value; preferLocalBuild = true; __structuredAttrs = true; } '' - valuePath="$TMPDIR/value" - printf "%s" "$value" > "$valuePath" - json2yaml "$valuePath" "$out" + json2yaml ${ + # attributes with null values are omitted from the JSON with structured attrs + # yaml_1_2Null test keeps this in check + if value == null then ''<(echo "null")'' else ''--unwrap value "$NIX_ATTRS_JSON_FILE"'' + } "$out" '' ) { }; @@ -958,8 +962,10 @@ optionalAttrs allowAliases aliases python3 black ]; - imports = toJSON (value._imports or [ ]); - value = toJSON (removeAttrs value [ "_imports" ]); + imports = value._imports or [ ]; + # value must be an attrset, type would verify that, + # otherwise removeAttrs will fail. + value = removeAttrs value [ "_imports" ]; pythonGen = pkgs.writeText "pythonGen" '' import json import os @@ -982,26 +988,20 @@ optionalAttrs allowAliases aliases else: return repr(value) - with open(os.environ["importsPath"], "r") as f: - imports = json.load(f) - if imports is not None: - for i in imports: + with open(os.environ["NIX_ATTRS_JSON_FILE"], "r") as f: + attrs = json.load(f) + if attrs["imports"] is not None: + for i in attrs["imports"]: print(f"import {i}") print() - with open(os.environ["valuePath"], "r") as f: - for key, value in json.load(f).items(): + for key, value in attrs["value"].items(): print(f"{key} = {recursive_repr(value)}") ''; preferLocalBuild = true; __structuredAttrs = true; } '' - export importsPath="$TMPDIR/imports" - printf "%s" "$imports" > "$importsPath" - export valuePath="$TMPDIR/value" - printf "%s" "$value" > "$valuePath" - cat "$valuePath" python3 "$pythonGen" > $out black $out '' @@ -1015,7 +1015,13 @@ optionalAttrs allowAliases aliases }: if format == "badgerfish" then { - type = serializableValueWith { typeName = "XML"; }; + type = + attrsOf (serializableValueWith { + typeName = "XML"; + }) + // { + description = "XML value"; + }; generate = name: value: @@ -1031,14 +1037,16 @@ optionalAttrs allowAliases aliases python3Packages.xmltodict libxml2Python ]; - value = toJSON value; + inherit value; pythonGen = pkgs.writeText "pythonGen" '' import json import os import xmltodict - with open(os.environ["valuePath"], "r") as f: - print(xmltodict.unparse(json.load(f), full_document=${ + with open(os.environ["NIX_ATTRS_JSON_FILE"], "r") as f: + value = json.load(f).get("value") + assert type(value) is dict, "value must be an attrset" + print(xmltodict.unparse(value, full_document=${ if withHeader then "True" else "False" }, pretty=True, indent=" " * 2)) ''; @@ -1046,8 +1054,6 @@ optionalAttrs allowAliases aliases __structuredAttrs = true; } '' - export valuePath="$TMPDIR/value" - printf "%s" "$value" > "$valuePath" python3 "$pythonGen" > $out xmllint $out > /dev/null '' diff --git a/pkgs/pkgs-lib/formats/configobj/default.nix b/pkgs/pkgs-lib/formats/configobj/default.nix index 8179c4412e5f..227d8e7aa990 100644 --- a/pkgs/pkgs-lib/formats/configobj/default.nix +++ b/pkgs/pkgs-lib/formats/configobj/default.nix @@ -3,10 +3,6 @@ pkgs, }: let - inherit (lib) - toJSON - ; - inherit (lib.types) serializableValueWith ; @@ -25,12 +21,12 @@ in (pkgs.python3.withPackages (ps: [ ps.configobj ])) ]; - valuesJSON = toJSON value; + inherit value; __structuredAttrs = true; strictDeps = true; } '' - printf "%s" "$valuesJSON" | python3 ${./generate.py} > "$out" + python3 ${./generate.py} > "$out" ''; }; } diff --git a/pkgs/pkgs-lib/formats/configobj/generate.py b/pkgs/pkgs-lib/formats/configobj/generate.py index 5863396c120d..e531e68a4fbb 100644 --- a/pkgs/pkgs-lib/formats/configobj/generate.py +++ b/pkgs/pkgs-lib/formats/configobj/generate.py @@ -1,7 +1,10 @@ import json +import os import sys from configobj import ConfigObj config = ConfigObj(interpolation=False, encoding="UTF8") -config.update(json.load(sys.stdin)) +with open(os.environ["NIX_ATTRS_JSON_FILE"]) as f: + value = json.load(f).get("value") +config.update(value) config.write(sys.stdout.buffer) diff --git a/pkgs/pkgs-lib/tests/formats.nix b/pkgs/pkgs-lib/tests/formats.nix index 1b10f1361038..b03b44c99150 100644 --- a/pkgs/pkgs-lib/tests/formats.nix +++ b/pkgs/pkgs-lib/tests/formats.nix @@ -142,6 +142,14 @@ runBuildTests { ''; }; + jsonNull = shouldPass { + format = formats.json { }; + input = null; + expected = '' + null + ''; + }; + yaml_1_1Atoms = shouldPass { format = formats.yaml_1_1 { }; input = { @@ -176,6 +184,15 @@ runBuildTests { ''; }; + yaml_1_1Null = shouldPass { + format = formats.yaml_1_1 { }; + input = null; + expected = '' + null + ... + ''; + }; + yaml_1_2Atoms = shouldPass { format = formats.yaml_1_2 { }; input = { @@ -210,6 +227,16 @@ runBuildTests { ''; }; + yaml_1_2Null = shouldPass { + format = formats.yaml_1_2 { }; + input = null; + # nixfmt insists on removing indentation, so force it with ${" "} + expected = '' + + ${" "}null + ''; + }; + iniAtoms = shouldPass { format = formats.ini { }; input = { @@ -884,6 +911,14 @@ runBuildTests { ''; }; + cdnNull = shouldPass { + format = formats.cdn { }; + input = null; + expected = '' + null: null + ''; + }; + # This test is responsible for # 1. testing type coercions # 2. providing a more readable example test @@ -994,6 +1029,14 @@ runBuildTests { ''; }; + luaNull = shouldPass { + format = formats.lua { }; + input = null; + expected = '' + return nil + ''; + }; + nixConfAtoms = shouldPass { format = formats.nixConf { package = pkgs.nix; @@ -1019,6 +1062,15 @@ runBuildTests { ''; }; + nixConfNull = shouldFail { + format = formats.nixConf { + package = pkgs.nix; + version = pkgs.nix.version; + extraOptions = "ignore-try = false"; + }; + input = null; + }; + phpAtoms = shouldPass rec { format = formats.php { finalVariable = "config"; }; input = { @@ -1048,6 +1100,16 @@ runBuildTests { ''; }; + phpNull = shouldPass { + format = formats.php { finalVariable = "config"; }; + input = null; + expected = '' + ''; }; + PlistNull = shouldPass { + format = formats.plist { }; + input = null; + expected = '' + + + + + ''; + }; + hcl1Atoms = shouldPass { format = formats.hcl1 { }; input = {