From ab2bc9944360bd1e202d8f70d4814bc579d3566f Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Tue, 30 Dec 2025 19:00:52 -0600 Subject: [PATCH 01/29] nixos/udev: Copy dmi_memory_id only if installed This tool is only built on certain architectures that systemd enumerates as supporting DMI[1]. Notably, this excludes POWER. [1]: https://github.com/systemd/systemd/blob/v259/meson.build#L1529-L1530 --- nixos/modules/services/hardware/udev.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix index 6e779df94e77..b8e7549b1322 100644 --- a/nixos/modules/services/hardware/udev.nix +++ b/nixos/modules/services/hardware/udev.nix @@ -462,10 +462,17 @@ in "${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd" "${config.boot.initrd.systemd.package}/lib/udev/ata_id" "${config.boot.initrd.systemd.package}/lib/udev/cdrom_id" - "${config.boot.initrd.systemd.package}/lib/udev/dmi_memory_id" "${config.boot.initrd.systemd.package}/lib/udev/scsi_id" "${config.boot.initrd.systemd.package}/lib/udev/rules.d" ] + ++ lib.optional ( + # https://github.com/systemd/systemd/blob/v259/meson.build#L1529-L1530 + pkgs.stdenv.hostPlatform.isx86 + || pkgs.stdenv.hostPlatform.isAarch + || pkgs.stdenv.hostPlatform.isLoongArch64 + || pkgs.stdenv.hostPlatform.isMips + || pkgs.stdenv.hostPlatform.isRiscV64 + ) "${config.boot.initrd.systemd.package}/lib/udev/dmi_memory_id" ++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages; # Generate the udev rules for the initrd From cdb35b67b3d9828b0eae3769281ffa0f84592882 Mon Sep 17 00:00:00 2001 From: Jacek Galowicz Date: Sat, 23 May 2026 13:02:53 +0200 Subject: [PATCH 02/29] nixos-test-driver: drop unneeded qemu dep --- nixos/lib/test-driver/default.nix | 3 --- nixos/lib/testing/driver.nix | 1 - pkgs/top-level/all-packages.nix | 1 - 3 files changed, 5 deletions(-) diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix index 77638397d11c..03cfcb6915b3 100644 --- a/nixos/lib/test-driver/default.nix +++ b/nixos/lib/test-driver/default.nix @@ -18,8 +18,6 @@ netpbm, vhost-device-vsock, nixosTests, - qemu_pkg ? qemu_test, - qemu_test, setuptools, socat, systemd, @@ -56,7 +54,6 @@ buildPythonApplication { propagatedBuildInputs = [ coreutils netpbm - qemu_pkg socat util-linux vde2 diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix index 69392eb052af..02dd359b20c3 100644 --- a/nixos/lib/testing/driver.nix +++ b/nixos/lib/testing/driver.nix @@ -15,7 +15,6 @@ let # the respective qemu version and with or without ocr support testDriver = config.pythonTestDriverPackage.override { inherit (config) enableOCR extraPythonPackages; - qemu_pkg = config.qemu.package; enableNspawn = config.containers != { }; }; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 412eb9b256dd..b2e11801d838 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -196,7 +196,6 @@ with pkgs; inherit (nix-update) nix-update-script; nixos-test-driver = pkgs.python3Packages.callPackage ../../nixos/lib/test-driver { - qemu_pkg = pkgs.qemu; imagemagick_light = pkgs.imagemagick_light.override { inherit (pkgs) libtiff; }; tesseract4 = pkgs.tesseract4.override { enableLanguages = [ "eng" ]; }; # We want `pkgs.systemd`, *not* `python3Packages.system`. From a3b079d14385f446643dfb43642dfd24cd2f1d4d Mon Sep 17 00:00:00 2001 From: Jacek Galowicz Date: Sun, 24 May 2026 08:57:07 +0200 Subject: [PATCH 03/29] nixos-tests: fix qemu-img paths --- nixos/tests/boot.nix | 2 +- nixos/tests/common/ec2.nix | 8 +- nixos/tests/ec2-image.nix | 482 +++++++++++++------------- nixos/tests/systemd-initrd-simple.nix | 16 +- nixos/tests/systemd.nix | 34 +- 5 files changed, 275 insertions(+), 267 deletions(-) diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix index d1ac1a8769fe..a59f94b94edf 100644 --- a/nixos/tests/boot.nix +++ b/nixos/tests/boot.nix @@ -207,7 +207,7 @@ in import os # Create a mutable linked image backed by the read-only SD image - if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0: + if os.system("${pkgs.qemu}/bin/qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0: raise RuntimeError("Could not create mutable linked image") machine = create_machine("${startCommand}") diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix index 864afc63cb8b..f28e094d455b 100644 --- a/nixos/tests/common/ec2.nix +++ b/nixos/tests/common/ec2.nix @@ -51,9 +51,11 @@ in ) os.makedirs(image_dir, mode=0o700, exist_ok=True) disk_image = os.path.join(image_dir, "machine.qcow2") + QEMU_BIN = "${pkgs.qemu}" + QEMU_IMG = f"{QEMU_BIN}/bin/qemu-img" subprocess.check_call( [ - "qemu-img", + QEMU_IMG, "create", "-f", "qcow2", @@ -64,7 +66,7 @@ in disk_image, ] ) - subprocess.check_call(["qemu-img", "resize", disk_image, "10G"]) + subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"]) # Note: we use net=169.0.0.0/8 rather than # net=169.254.0.0/16 to prevent dhcpcd from getting horribly @@ -74,7 +76,7 @@ in # turn off the DHCP server, but qemu does not have an option # to do that. start_command = ( - "qemu-kvm -m 1024" + f"{QEMU_BIN}/bin/qemu-kvm -m 1024" + " -device virtio-net-pci,netdev=vlan0" + " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${getExe imdsServer} ${metaData}'" + f" -drive file={disk_image},if=virtio,werror=report" diff --git a/nixos/tests/ec2-image.nix b/nixos/tests/ec2-image.nix index b54b153bd52e..df07d14b8ba7 100644 --- a/nixos/tests/ec2-image.nix +++ b/nixos/tests/ec2-image.nix @@ -88,294 +88,298 @@ in }; }; - testScript = '' - import os - import re - import subprocess - import tempfile + testScript = + { nodes, ... }: + '' + import os + import re + import subprocess + import tempfile - # Instance Metadata Service (IMDSv2 with 1.0 metadata version) - # TODO: Use 'latest' metadata version instead of '1.0' - # TODO: [Test matrix] also test providing the host key through IMDS - # - i.e. a test module argument to select between writing or reading the host key - def create_ec2_metadata_dir(temp_dir, client_pubkey): - """Create fake EC2 metadata directory structure with mock data""" - metadata_dir = os.path.join(temp_dir.name, "ec2-metadata") + # Instance Metadata Service (IMDSv2 with 1.0 metadata version) + # TODO: Use 'latest' metadata version instead of '1.0' + # TODO: [Test matrix] also test providing the host key through IMDS + # - i.e. a test module argument to select between writing or reading the host key + def create_ec2_metadata_dir(temp_dir, client_pubkey): + """Create fake EC2 metadata directory structure with mock data""" + metadata_dir = os.path.join(temp_dir.name, "ec2-metadata") - # Create directory structure - os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True) - os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True) + # Create directory structure + os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True) + os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True) - # Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches) - with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f: - f.write("test-instance") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f: - f.write("(test)") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f: - f.write("i-1234567890abcdef0") - with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f: - f.write("") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f: - f.write(client_pubkey) + # Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches) + with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f: + f.write("test-instance") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f: + f.write("(test)") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f: + f.write("i-1234567890abcdef0") + with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f: + f.write("") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f: + f.write(client_pubkey) - # IMDSv2 token endpoint - return a fake token - with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f: - f.write("test-token-12345") + # IMDSv2 token endpoint - return a fake token + with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f: + f.write("test-token-12345") - return metadata_dir + return metadata_dir - def generate_client_ssh_key(): - """Generate SSH key pair on VM host for client authentication""" - # Use temporary directory for key generation - import tempfile - with tempfile.TemporaryDirectory() as key_dir: - private_key = os.path.join(key_dir, "id_ed25519") - public_key = os.path.join(key_dir, "id_ed25519.pub") + def generate_client_ssh_key(): + """Generate SSH key pair on VM host for client authentication""" + # Use temporary directory for key generation + import tempfile + with tempfile.TemporaryDirectory() as key_dir: + private_key = os.path.join(key_dir, "id_ed25519") + public_key = os.path.join(key_dir, "id_ed25519.pub") - # Generate key pair using host SSH tools - ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"") - if ret != 0: - raise Exception("Failed to generate SSH key pair") + # Generate key pair using host SSH tools + ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"") + if ret != 0: + raise Exception("Failed to generate SSH key pair") - # Read the generated public key - with open(public_key, "r") as f: - client_pubkey = f.read().strip() + # Read the generated public key + with open(public_key, "r") as f: + client_pubkey = f.read().strip() - # Read the private key - with open(private_key, "r") as f: - client_private_key = f.read() + # Read the private key + with open(private_key, "r") as f: + client_private_key = f.read() - return client_pubkey, client_private_key + return client_pubkey, client_private_key - def setup_client_ssh_key(client, client_private_key): - """Install the pre-generated SSH private key on client""" - client.succeed("mkdir -p /root/.ssh") - client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF") - client.succeed("chmod 600 /root/.ssh/id_ed25519") + def setup_client_ssh_key(client, client_private_key): + """Install the pre-generated SSH private key on client""" + client.succeed("mkdir -p /root/.ssh") + client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF") + client.succeed("chmod 600 /root/.ssh/id_ed25519") - def setup_machine(temp_dir, client_pubkey): - """Initialize EC2 machine with disk image, metadata server, and networking""" - # Set up disk image - image_dir = os.path.join( - os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine" - ) - os.makedirs(image_dir, mode=0o700, exist_ok=True) - disk_image = os.path.join(image_dir, "machine.qcow2") - subprocess.check_call([ - "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", - "-o", "backing_file=${image}", disk_image - ]) - subprocess.check_call(["qemu-img", "resize", disk_image, "10G"]) + def setup_machine(temp_dir, client_pubkey): + """Initialize EC2 machine with disk image, metadata server, and networking""" + # Set up disk image + image_dir = os.path.join( + os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine" + ) + os.makedirs(image_dir, mode=0o700, exist_ok=True) + disk_image = os.path.join(image_dir, "machine.qcow2") + QEMU = "${nodes.machine.virtualisation.qemu.package}" + QEMU_IMG = f"{QEMU}/bin/qemu-img" + subprocess.check_call([ + QEMU_IMG, "create", "-f", "qcow2", "-F", "qcow2", + "-o", "backing_file=${image}", disk_image + ]) + subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"]) - # Create fake EC2 metadata in temporary directory with client's public key - metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey) + # Create fake EC2 metadata in temporary directory with client's public key + metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey) - # Add both VLAN networking (matching test framework) and EC2 metadata server - vlan_net = ( - " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02" - + ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"' - ) - metadata_net = ( - " -device virtio-net-pci,netdev=ec2meta" - + f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'" - ) + # Add both VLAN networking (matching test framework) and EC2 metadata server + vlan_net = ( + " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02" + + ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"' + ) + metadata_net = ( + " -device virtio-net-pci,netdev=ec2meta" + + f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'" + ) - start_command = ( - "qemu-kvm -m 1024" - + f" -drive file={disk_image},if=virtio,werror=report" - + vlan_net - + metadata_net - + " $QEMU_OPTS" - ) + start_command = ( + f"{QEMU}/bin/qemu-kvm -m 1024" + + f" -drive file={disk_image},if=virtio,werror=report" + + vlan_net + + metadata_net + + " $QEMU_OPTS" + ) - return create_machine(start_command), metadata_dir + return create_machine(start_command), metadata_dir - def test_userdata_decompression(machine, user_data_path, compressed_data, format_name): - """Test that compressed user-data is decompressed by fetch-ec2-metadata""" - test_marker = f"{format_name}-decompression-test" - with open(user_data_path, "wb") as f: - f.write(compressed_data) - machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata") - result = machine.succeed("cat /etc/ec2-metadata/user-data") - assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}" - journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b") - assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}" + def test_userdata_decompression(machine, user_data_path, compressed_data, format_name): + """Test that compressed user-data is decompressed by fetch-ec2-metadata""" + test_marker = f"{format_name}-decompression-test" + with open(user_data_path, "wb") as f: + f.write(compressed_data) + machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata") + result = machine.succeed("cat /etc/ec2-metadata/user-data") + assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}" + journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b") + assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}" - # Create temporary directory for metadata (scoped for cleanup) - temp_dir = tempfile.TemporaryDirectory() + # Create temporary directory for metadata (scoped for cleanup) + temp_dir = tempfile.TemporaryDirectory() - # Start client first (but don't wait for it to boot) - client.start() + # Start client first (but don't wait for it to boot) + client.start() - # Generate SSH key pair on VM host before starting machine - client_pubkey, client_private_key = generate_client_ssh_key() + # Generate SSH key pair on VM host before starting machine + client_pubkey, client_private_key = generate_client_ssh_key() - # Set up machine with client's public key in metadata service - machine, metadata_dir = setup_machine(temp_dir, client_pubkey) - user_data_path = os.path.join(metadata_dir, "1.0", "user-data") + # Set up machine with client's public key in metadata service + machine, metadata_dir = setup_machine(temp_dir, client_pubkey) + user_data_path = os.path.join(metadata_dir, "1.0", "user-data") - try: - machine.start() + try: + machine.start() - # Wait for services to be ready - machine.wait_for_unit("sshd.service") - machine.wait_for_unit("print-host-key.service") - machine.wait_for_unit("apply-ec2-data.service") + # Wait for services to be ready + machine.wait_for_unit("sshd.service") + machine.wait_for_unit("print-host-key.service") + machine.wait_for_unit("apply-ec2-data.service") - # Extract shared variables outside subtests - machine_ip = "${config.nodes.machine.networking.primaryIPAddress}" + # Extract shared variables outside subtests + machine_ip = "${config.nodes.machine.networking.primaryIPAddress}" - with subtest("EC2 metadata service connectivity"): - # Obtain an IMDSv2 token, then use it to fetch metadata - imds_token = machine.succeed( - "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" - " http://169.254.169.254/latest/api/token" - ).strip() - assert imds_token, "Failed to obtain IMDSv2 token" - hostname_response = machine.succeed( - f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'" - " http://169.254.169.254/1.0/meta-data/hostname" - ) - assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}" + with subtest("EC2 metadata service connectivity"): + # Obtain an IMDSv2 token, then use it to fetch metadata + imds_token = machine.succeed( + "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" + " http://169.254.169.254/latest/api/token" + ).strip() + assert imds_token, "Failed to obtain IMDSv2 token" + hostname_response = machine.succeed( + f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'" + " http://169.254.169.254/1.0/meta-data/hostname" + ) + assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}" - with subtest("SSH host key extraction from console"): - console_log = machine.get_console_log() - assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log - assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log - assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log - assert "-----END SSH HOST KEY KEYS-----" in console_log + with subtest("SSH host key extraction from console"): + console_log = machine.get_console_log() + assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log + assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log + assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log + assert "-----END SSH HOST KEY KEYS-----" in console_log - keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" - keys_match = re.search(keys_pattern, console_log, re.DOTALL) - assert keys_match, "Could not find SSH host keys section" - keys_content = keys_match.group(1).strip() - assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix" + keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" + keys_match = re.search(keys_pattern, console_log, re.DOTALL) + assert keys_match, "Could not find SSH host keys section" + keys_content = keys_match.group(1).strip() + assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix" - with subtest("Network connectivity"): - client.succeed(f"ping -c 1 {machine_ip}") + with subtest("Network connectivity"): + client.succeed(f"ping -c 1 {machine_ip}") - with subtest("SSH connectivity with strict host key checking"): - # Install the pre-generated private key on client - setup_client_ssh_key(client, client_private_key) + with subtest("SSH connectivity with strict host key checking"): + # Install the pre-generated private key on client + setup_client_ssh_key(client, client_private_key) - # Get console log and extract host keys - console_log = machine.get_console_log() - keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" - keys_match = re.search(keys_pattern, console_log, re.DOTALL) - assert keys_match, "Could not find SSH host keys section" + # Get console log and extract host keys + console_log = machine.get_console_log() + keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" + keys_match = re.search(keys_pattern, console_log, re.DOTALL) + assert keys_match, "Could not find SSH host keys section" - # Create known_hosts file from console-extracted host keys - keys_content = keys_match.group(1).strip() - known_hosts_entries = [] - for line in keys_content.split('\n'): - if line.strip() and line.startswith('ssh-'): - known_hosts_entries.append(f"{machine_ip} {line.strip()}") + # Create known_hosts file from console-extracted host keys + keys_content = keys_match.group(1).strip() + known_hosts_entries = [] + for line in keys_content.split('\n'): + if line.strip() and line.startswith('ssh-'): + known_hosts_entries.append(f"{machine_ip} {line.strip()}") - assert known_hosts_entries, "No SSH host keys found for known_hosts generation" + assert known_hosts_entries, "No SSH host keys found for known_hosts generation" - known_hosts_content = '\n'.join(known_hosts_entries) - client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF") + known_hosts_content = '\n'.join(known_hosts_entries) + client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF") - # Test SSH connectivity with strict host key checking - ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'") - assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}" + # Test SSH connectivity with strict host key checking + ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'") + assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}" - with subtest("Basic EC2 functionality"): - machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'") + with subtest("Basic EC2 functionality"): + machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'") - with subtest("Decompression of gzip-compressed user-data"): - import gzip as gzip_mod - test_data = b"#!/bin/bash\necho gzip-decompression-test\n" - test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip") + with subtest("Decompression of gzip-compressed user-data"): + import gzip as gzip_mod + test_data = b"#!/bin/bash\necho gzip-decompression-test\n" + test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip") - with subtest("Decompression of bzip2-compressed user-data"): - import bz2 - test_data = b"#!/bin/bash\necho bzip2-decompression-test\n" - test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2") + with subtest("Decompression of bzip2-compressed user-data"): + import bz2 + test_data = b"#!/bin/bash\necho bzip2-decompression-test\n" + test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2") - with subtest("Decompression of xz-compressed user-data"): - import lzma - test_data = b"#!/bin/bash\necho xz-decompression-test\n" - test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz") + with subtest("Decompression of xz-compressed user-data"): + import lzma + test_data = b"#!/bin/bash\necho xz-decompression-test\n" + test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz") - with subtest("Decompression of zstd-compressed user-data"): - test_data = b"#!/bin/bash\necho zstd-decompression-test\n" - proc = subprocess.run( - ["${hostPkgs.zstd}/bin/zstd", "-c"], - input=test_data, capture_output=True, check=True, - ) - test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd") + with subtest("Decompression of zstd-compressed user-data"): + test_data = b"#!/bin/bash\necho zstd-decompression-test\n" + proc = subprocess.run( + ["${hostPkgs.zstd}/bin/zstd", "-c"], + input=test_data, capture_output=True, check=True, + ) + test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd") - with subtest("Decompression of lzip-compressed user-data"): - test_data = b"#!/bin/bash\necho lzip-decompression-test\n" - proc = subprocess.run( - ["${hostPkgs.lzip}/bin/lzip", "-c"], - input=test_data, capture_output=True, check=True, - ) - test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip") + with subtest("Decompression of lzip-compressed user-data"): + test_data = b"#!/bin/bash\necho lzip-decompression-test\n" + proc = subprocess.run( + ["${hostPkgs.lzip}/bin/lzip", "-c"], + input=test_data, capture_output=True, check=True, + ) + test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip") - with subtest("IPv6 IMDS fallback"): - # Save hostname fetched via IPv4 for later comparison - original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() + with subtest("IPv6 IMDS fallback"): + # Save hostname fetched via IPv4 for later comparison + original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() - # Assign the EC2 IPv6 IMDS address to loopback - machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo") + # Assign the EC2 IPv6 IMDS address to loopback + machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo") - # Create metadata directory structure for the IPv6 endpoint - machine.succeed( - "mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0" - " && mkdir -p /tmp/ipv6-metadata/latest/api" - " && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname" - " && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path" - " && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id" - " && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token" - " && touch /tmp/ipv6-metadata/1.0/user-data" - ) - machine.execute( - "test -f /etc/ec2-metadata/public-keys-0-openssh-key" - " && cp /etc/ec2-metadata/public-keys-0-openssh-key" - " /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key" - ) + # Create metadata directory structure for the IPv6 endpoint + machine.succeed( + "mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0" + " && mkdir -p /tmp/ipv6-metadata/latest/api" + " && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname" + " && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path" + " && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id" + " && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token" + " && touch /tmp/ipv6-metadata/1.0/user-data" + ) + machine.execute( + "test -f /etc/ec2-metadata/public-keys-0-openssh-key" + " && cp /etc/ec2-metadata/public-keys-0-openssh-key" + " /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key" + ) - # Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style) - machine.succeed( - "systemd-run --unit=ipv6-imds --" - " socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr" - " SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'" - ) + # Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style) + machine.succeed( + "systemd-run --unit=ipv6-imds --" + " socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr" + " SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'" + ) - # Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth) - machine.wait_until_succeeds( - "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" - " http://[fd00:ec2::254]/latest/api/token" - ) + # Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth) + machine.wait_until_succeeds( + "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" + " http://[fd00:ec2::254]/latest/api/token" + ) - # Block IPv4 IMDS to force fallback to IPv6 - machine.succeed( - "iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" - ) + # Block IPv4 IMDS to force fallback to IPv6 + machine.succeed( + "iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" + ) - # Verify IPv4 IMDS is now unreachable - machine.fail( - "curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname" - ) + # Verify IPv4 IMDS is now unreachable + machine.fail( + "curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname" + ) - # Clear fetched metadata and re-run the fetcher - machine.succeed("rm -f /etc/ec2-metadata/*") - machine.succeed("systemctl restart fetch-ec2-metadata") + # Clear fetched metadata and re-run the fetcher + machine.succeed("rm -f /etc/ec2-metadata/*") + machine.succeed("systemctl restart fetch-ec2-metadata") - # Verify metadata was successfully re-fetched via IPv6 - hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() - assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'" + # Verify metadata was successfully re-fetched via IPv6 + hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() + assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'" - # Clean up: restore IPv4 IMDS access - machine.succeed( - "iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" - ) - machine.succeed("systemctl stop ipv6-imds") + # Clean up: restore IPv4 IMDS access + machine.succeed( + "iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" + ) + machine.succeed("systemctl stop ipv6-imds") - finally: - machine.shutdown() - temp_dir.cleanup() - ''; + finally: + machine.shutdown() + temp_dir.cleanup() + ''; } diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix index 191a23abb2e4..b02616e1b772 100644 --- a/nixos/tests/systemd-initrd-simple.nix +++ b/nixos/tests/systemd-initrd-simple.nix @@ -1,15 +1,15 @@ +{ pkgs, ... }: { name = "systemd-initrd-simple"; - nodes.machine = - { pkgs, ... }: - { - testing.initrdBackdoor = true; - boot.initrd.systemd.enable = true; - virtualisation.fileSystems."/".autoResize = true; - }; + nodes.machine = { + testing.initrdBackdoor = true; + boot.initrd.systemd.enable = true; + virtualisation.fileSystems."/".autoResize = true; + }; testScript = + { nodes, ... }: # python '' import subprocess @@ -43,7 +43,7 @@ oldAvail = machine.succeed("df --output=avail / | sed 1d") machine.shutdown() - subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) + subprocess.check_call(["${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) machine.start() machine.switch_root() diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix index d04b3a374570..3c467daf4ae7 100644 --- a/nixos/tests/systemd.nix +++ b/nixos/tests/systemd.nix @@ -38,21 +38,23 @@ services.journald.extraConfig = "Storage=volatile"; test-support.displayManager.auto.user = "alice"; - systemd.shutdown.test = pkgs.writeScript "test.shutdown" '' - #!${pkgs.runtimeShell} - PATH=${ - lib.makeBinPath ( - with pkgs; - [ - util-linux - coreutils - ] - ) - } - mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared - touch /tmp/shared/shutdown-test - umount /tmp/shared - ''; + systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/test".source = + pkgs.writeShellScript "test.shutdown" '' + PATH=${ + lib.makeBinPath ( + with pkgs; + [ + util-linux + coreutils + ] + ) + } + mkdir -p /tmp/shared + mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared + touch /tmp/shared/shutdown-test + umount /tmp/shared + ''; + systemd.shutdownRamfs.storePaths = [ "${pkgs.util-linux}/bin" ]; systemd.services.oncalendar-test = { description = "calendar test"; @@ -147,7 +149,7 @@ subprocess.check_call( [ - "qemu-img", + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "convert", "-O", "raw", From cc181ff3310e63d5f77b88e4df563b2b24b42bcc Mon Sep 17 00:00:00 2001 From: Jost Alemann Date: Wed, 24 Jun 2026 09:56:48 +0200 Subject: [PATCH 04/29] ruff: 0.15.18 -> 0.15.19 Changelog: https://github.com/astral-sh/ruff/releases/tag/0.15.19 Diff: https://github.com/astral-sh/ruff/compare/0.15.18...0.15.19 --- 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 ca5d02475508..86e800f61978 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.18"; + version = "0.15.19"; __structuredAttrs = true; @@ -24,12 +24,12 @@ rustPlatform.buildRustPackage (finalAttrs: { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-DH00tENXdCdNcGPXPGzZsU3RVYQ0VBe1QLvbgEg/G6k="; + hash = "sha256-L0QRzc9S7vHOkk3WsfT0kgjBKbApsCHTgqCv4ts1j9g="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-RmK14NMPbhoRVLwIF5GXdGQg2AnMH20JGx7XF4HO+wY="; + cargoHash = "sha256-DapI+Wv4KNlbGUCRIiAybyKNYipRIFd5O2fYy9Uatjg="; nativeBuildInputs = [ installShellFiles ]; From ae557015a9ccf0ceb4ae8b486490c3c850baa797 Mon Sep 17 00:00:00 2001 From: Jost Alemann Date: Wed, 24 Jun 2026 16:04:07 +0200 Subject: [PATCH 05/29] ty: 0.0.52 -> 0.0.53 Changelog: https://github.com/astral-sh/ty/releases/tag/0.0.53 Diff: https://github.com/astral-sh/ty/compare/0.0.52...0.0.53 --- pkgs/by-name/ty/ty/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ty/ty/package.nix b/pkgs/by-name/ty/ty/package.nix index 48a4dbae05f2..ca6f502ade0f 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.52"; + version = "0.0.53"; __structuredAttrs = true; src = fetchFromGitHub { @@ -25,7 +25,7 @@ rustPlatform.buildRustPackage (finalAttrs: { repo = "ty"; tag = finalAttrs.version; fetchSubmodules = true; - hash = "sha256-p9fTzQ4DYvnwrtLdpSTekBV4ZbR1KETR8dfsbp8CDpo="; + hash = "sha256-0msHGoPm2b7OX0Lax/yr2Uism1RxsH4DxhxlQP/aFpU="; }; # For Darwin platforms, remove the integration test for file notifications, @@ -39,7 +39,7 @@ rustPlatform.buildRustPackage (finalAttrs: { cargoBuildFlags = [ "--package=ty" ]; - cargoHash = "sha256-NUIdYOeyRsR/ZQueEXshYdWTnSeQiRjZRRi2ag8Dm48="; + cargoHash = "sha256-DapI+Wv4KNlbGUCRIiAybyKNYipRIFd5O2fYy9Uatjg="; nativeBuildInputs = [ installShellFiles ]; buildInputs = [ rust-jemalloc-sys ]; From 8651ab7d4d794604b937906cd65dc1344cd16233 Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Fri, 16 Jan 2026 21:43:11 +0100 Subject: [PATCH 06/29] nixos/systemd/tmpfiles: add credstore.conf This creates the /{etc,run}/credstore{,.encrypted} directories from which ImportCredential= picks up credentials --- nixos/modules/system/boot/systemd/tmpfiles.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix index c606ff1ac50b..631a02645d9b 100644 --- a/nixos/modules/system/boot/systemd/tmpfiles.nix +++ b/nixos/modules/system/boot/systemd/tmpfiles.nix @@ -317,6 +317,7 @@ in mkdir -p $out/lib/tmpfiles.d cd $out/lib/tmpfiles.d + ln -s "${systemd}/example/tmpfiles.d/credstore.conf" ln -s "${systemd}/example/tmpfiles.d/home.conf" ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" ln -s "${systemd}/example/tmpfiles.d/portables.conf" From d4d4a78d32591023662c8da385e38e0f360de4b7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Wed, 24 Jun 2026 18:21:40 +0100 Subject: [PATCH 07/29] nano: 9.0 -> 9.1 Changes: https://www.nano-editor.org/news.php --- pkgs/by-name/na/nano/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/na/nano/package.nix b/pkgs/by-name/na/nano/package.nix index e4a2d0b1ea96..2ab07429d9e6 100644 --- a/pkgs/by-name/na/nano/package.nix +++ b/pkgs/by-name/na/nano/package.nix @@ -31,11 +31,11 @@ let in stdenv.mkDerivation rec { pname = "nano"; - version = "9.0"; + version = "9.1"; src = fetchurl { url = "mirror://gnu/nano/${pname}-${version}.tar.xz"; - hash = "sha256-nzhDdLSWEQoltzrVpf67OEeDxuMYizcGP2d6yQgBP94="; + hash = "sha256-X0d2QnTLdTI0nOCqIOwQ8ejoUabp+j62aBLEPRltsEI="; }; nativeBuildInputs = [ texinfo ] ++ lib.optional enableNls gettext; From 1fc433dc5174ec33192b1d59f6e65c9beed7e9ef Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:35:49 +0200 Subject: [PATCH 08/29] nixos/systemd/repart: add Varlink socket This enables the ability to use systemd-repart over Varlink at /run/systemd/io.systemd.Repart. Change-Id: Ia74fdf8c2cbc4ec52994ba8ceb5796acc731abd3 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 5 +++++ nixos/modules/system/boot/systemd/repart.nix | 6 ++++++ pkgs/os-specific/linux/systemd/default.nix | 1 + 3 files changed, 12 insertions(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 75b4143d6810..f218863ecf05 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -209,6 +209,11 @@ let "dbus-org.freedesktop.portable1.service" "systemd-portabled.service" ] + ++ optionals cfg.package.withRepart [ + # Varlink APIs + "systemd-repart@.service" + "systemd-repart.socket" + ] ++ [ "systemd-exit.service" "systemd-update-done.service" diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix index fce8e417991d..2126464c5250 100644 --- a/nixos/modules/system/boot/systemd/repart.nix +++ b/nixos/modules/system/boot/systemd/repart.nix @@ -157,6 +157,12 @@ in boot.initrd.systemd = lib.mkIf initrdCfg.enable { additionalUpstreamUnits = [ "systemd-repart.service" + # Varlink APIs + # NOTE: compared to stage 2 where the IPC is enabled in the global location, initrd + # might be optimized to keep away the repart binary. + # As a result, we enable repart IPC in the initrd only if repart is enabled in the initrd. + "systemd-repart.socket" + "systemd-repart@.service" ]; storePaths = [ diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index f0fb666cd8de..47dc60f1d20b 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -703,6 +703,7 @@ stdenv.mkDerivation (finalAttrs: { withMachined withNetworkd withNspawn + withRepart withPortabled withSysupdate withTimedated From 7dacd14febf4c7b8247ce2033e9bcf0faadae76a Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:43:24 +0200 Subject: [PATCH 09/29] nixos/systemd/ask-password: add Varlink API This adds io.systemd.AskPassword Varlink API by default. Change-Id: I45b9a53d489ec3ea5561006c9c91ccb7016b3ee1 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index f218863ecf05..ee6a64189c46 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -157,6 +157,8 @@ let "systemd-ask-password-wall.service" # Varlink APIs + "systemd-ask-password@.service" + "systemd-ask-password.socket" ] ++ lib.optionals cfg.package.withBootloader [ "systemd-bootctl@.service" From 3003651b5ab140b6287de122cebdd7098890d60a Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:43:55 +0200 Subject: [PATCH 10/29] nixos/systemd/importd: add Varlink API This adds io.systemd.Import by default. Change-Id: Idad216f83af2f3402b864a162373f04968725b7b Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index ee6a64189c46..81acb33df53b 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -180,6 +180,7 @@ let ] ++ optionals cfg.package.withImportd [ "systemd-importd.service" + "systemd-importd.socket" ] ++ optionals cfg.package.withMachined [ "machine.slice" From ff55fdd00331493823ddd2fad7d898ed70d9ac13 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:44:30 +0200 Subject: [PATCH 11/29] nixos/systemd/machined: add Varlink API This adds io.systemd.Machine by default. Change-Id: I05e595d22ad06504e2b3aacb48c74e7683057ce3 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 81acb33df53b..41ccdcd0e4bb 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -186,6 +186,7 @@ let "machine.slice" "machines.target" "systemd-machined.service" + "systemd-machined.socket" ] ++ optionals cfg.package.withNspawn [ "systemd-nspawn@.service" From 65cdcfc1331469f005923fd2dcbe305348f76631 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:44:55 +0200 Subject: [PATCH 12/29] nixos/systemd/mute-console: add Varlink API This adds io.systemd.MuteConsole by default. Change-Id: I613d5f12e5b2742206c3023e4d57ccdfbaf0e704 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 41ccdcd0e4bb..71b166b998a9 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -195,6 +195,9 @@ let # Misc. "systemd-sysctl.service" "systemd-machine-id-commit.service" + + "systemd-mute-console@.service" + "systemd-mute-console.socket" ] ++ optionals cfg.package.withTimedated [ "dbus-org.freedesktop.timedate1.service" From b8b65f1616d2b913f82324344315937650adae58 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:45:16 +0200 Subject: [PATCH 13/29] nixos/systemd/factory-reset: add Varlink API This adds io.systemd.FactoryReset by default. Change-Id: I2ef8ae8812e76bc81a5f962e9bfeb99967f6df83 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 71b166b998a9..4149cda3e1e5 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -233,6 +233,8 @@ let "factory-reset.target" "systemd-factory-reset-request.service" "systemd-factory-reset-reboot.service" + "systemd-factory-reset@.service" + "systemd-factory-reset.socket" ] ++ cfg.additionalUpstreamSystemUnits; From 04f7553ac2a5ecd6f01f3811eee6cabbbea8a8c7 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:49:52 +0200 Subject: [PATCH 14/29] nixos/systemd/networkd: add Varlink APIs This adds: - /run/systemd/resolve.hook/io.systemd.Network (DNS hook) - /run/systemd/report/io.systemd.Network (metrics) - /run/systemd/netif/io.systemd.Network (management) Change-Id: I3d63b0723ae5f8f9496418d8f5d857809302155e Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/networkd.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 2b14caac86fa..4a22d6585eb9 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -4231,8 +4231,10 @@ let "systemd-networkd-wait-online@.service" "systemd-networkd.service" "systemd-networkd.socket" - "systemd-networkd-persistent-storage.service" + "systemd-networkd-resolve-hook.socket" "systemd-networkd-varlink-metrics.socket" + "systemd-networkd-varlink.socket" + "systemd-networkd-persistent-storage.service" ]; systemd.sockets.systemd-networkd-varlink-metrics.wantedBy = [ "sockets.target" ]; @@ -4314,6 +4316,9 @@ let systemd.additionalUpstreamUnits = [ "systemd-networkd-wait-online.service" "systemd-networkd.service" + "systemd-networkd-resolve-hook.socket" + "systemd-networkd-varlink-metrics.socket" + "systemd-networkd-varlink.socket" "systemd-networkd.socket" "systemd-network-generator.service" "network-online.target" From 5c6a1e2b3d5a79cea0e4941ebaffef9226cccd28 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:51:05 +0200 Subject: [PATCH 15/29] nixos/systemd/udevd: add Varlink API This adds io.systemd.Udev by default. Change-Id: I41fec3831049aec03041a9ac8a282088e1773b79 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 4149cda3e1e5..acfb213b98e0 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -65,6 +65,7 @@ let # Udev. "systemd-udevd-control.socket" "systemd-udevd-kernel.socket" + "systemd-udevd-varlink.socket" "systemd-udevd.service" ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") From 13684b8470a62303a3659b759a44fc2eb1112664 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:52:02 +0200 Subject: [PATCH 16/29] nixos/systemd/resolved: add Varlink APIs This adds: - /run/systemd/resolve/io.systemd.Resolve (resolution) - /run/systemd/resolve/io.systemd.Resolve.Monitor (monitoring) Change-Id: I43f22be5c42757f3dad1e4a9d209b0a91f404a55 Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/resolved.nix | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix index da2b57285a76..6760dab044e9 100644 --- a/nixos/modules/system/boot/resolved.nix +++ b/nixos/modules/system/boot/resolved.nix @@ -185,7 +185,11 @@ in # added with order 501 to allow modules to go before with mkBefore system.nssDatabases.hosts = (mkOrder 501 [ "resolve [!UNAVAIL=return]" ]); - systemd.additionalUpstreamSystemUnits = [ "systemd-resolved.service" ]; + systemd.additionalUpstreamSystemUnits = [ + "systemd-resolved.service" + "systemd-resolved-monitor.socket" + "systemd-resolved-varlink.socket" + ]; systemd.services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; @@ -248,7 +252,12 @@ in tmpfiles.settings.systemd-resolved-stub."/etc/resolv.conf".L.argument = "/run/systemd/resolve/stub-resolv.conf"; - additionalUpstreamUnits = [ "systemd-resolved.service" ]; + additionalUpstreamUnits = [ + "systemd-resolved.service" + "systemd-resolved-monitor.socket" + "systemd-resolved-varlink.socket" + ]; + users.systemd-resolve = { }; groups.systemd-resolve = { }; storePaths = [ "${config.boot.initrd.systemd.package}/lib/systemd/systemd-resolved" ]; From 588eb06fccd1087ecbcff36bfa9da229b1fbf9be Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 21:53:21 +0200 Subject: [PATCH 17/29] nixos/systemd/tpm2/pcrextend: add Varlink API This adds io.systemd.PCRExtend if the system has TPM2 enabled. Additionally, this requires the system to fullfill ConditionSecurity=measured-uki. Change-Id: I3f1046cdc7463f3b6d3205030f7f12ac95e5cd9e Signed-off-by: Raito Bezarius --- nixos/modules/system/boot/systemd/tpm2.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/systemd/tpm2.nix b/nixos/modules/system/boot/systemd/tpm2.nix index 2703d60428f1..de0f72e5a543 100644 --- a/nixos/modules/system/boot/systemd/tpm2.nix +++ b/nixos/modules/system/boot/systemd/tpm2.nix @@ -43,6 +43,8 @@ "tpm2.target" "systemd-tpm2-setup-early.service" "systemd-tpm2-setup.service" + "systemd-pcrextend.socket" + "systemd-pcrextend@.service" ]; } ) @@ -69,6 +71,8 @@ boot.initrd.systemd.additionalUpstreamUnits = [ "tpm2.target" "systemd-tpm2-setup-early.service" + "systemd-pcrextend.socket" + "systemd-pcrextend@.service" ]; boot.initrd.availableKernelModules = [ @@ -81,6 +85,7 @@ pkgs.tpm2-tss "${cfg.package}/lib/systemd/systemd-tpm2-setup" "${cfg.package}/lib/systemd/system-generators/systemd-tpm2-generator" + "${cfg.package}/lib/systemd/systemd-pcrextend" ]; } ) @@ -89,7 +94,9 @@ cfg = config.boot.initrd.systemd; in lib.mkIf (cfg.enable && cfg.tpm2.enable && cfg.tpm2.pcrphases.enable) { - boot.initrd.systemd.additionalUpstreamUnits = [ "systemd-pcrphase-initrd.service" ]; + boot.initrd.systemd.additionalUpstreamUnits = [ + "systemd-pcrphase-initrd.service" + ]; boot.initrd.systemd.services.systemd-pcrphase-initrd.wantedBy = [ "initrd.target" ]; boot.initrd.systemd.storePaths = [ "${cfg.package}/lib/systemd/systemd-pcrextend" ]; } From 7a95244dd85e6d721ac568717bd8e792668b17f5 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 20 Jun 2026 22:41:38 +0200 Subject: [PATCH 18/29] nixos/tests/systemd-varlink: init This maintains the fact that some IPCs are now observed and becomes "public API" in NixOS as long as systemd makes them public APIs. This can be generalized to a generic public-varlink-apis test once other vendors obtains APIs which are defacto in the system, e.g. NixOS's IPCs. Change-Id: I182965b0cd043b55bc7bbab572c9f0d50142eda9 Signed-off-by: Raito Bezarius --- nixos/tests/all-tests.nix | 1 + nixos/tests/systemd-varlink.nix | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 nixos/tests/systemd-varlink.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 93ec2703c3e5..23d4b14284ba 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1676,6 +1676,7 @@ in systemd-user-settings = runTest ./systemd-user-settings.nix; systemd-user-tmpfiles-rules = runTest ./systemd-user-tmpfiles-rules.nix; systemd-userdbd = runTest ./systemd-userdbd.nix; + systemd-varlink = runTest ./systemd-varlink.nix; systemtap = handleTest ./systemtap.nix { }; szurubooru = handleTest ./szurubooru.nix { }; taler = handleTest ./taler { }; diff --git a/nixos/tests/systemd-varlink.nix b/nixos/tests/systemd-varlink.nix new file mode 100644 index 000000000000..df5d7cbb73a3 --- /dev/null +++ b/nixos/tests/systemd-varlink.nix @@ -0,0 +1,46 @@ +{ lib, ... }: +{ + name = "systemd-varlink"; + meta.maintainers = [ lib.maintainers.raitobezarius ]; + nodes.machine = + { config, pkgs, ... }: + { + networking.useNetworkd = true; + services.resolved.enable = true; + systemd.network.enable = true; + }; + testScript = '' + def list_interfaces(intf_path: str) -> list[str]: + return machine.succeed(f"varlinkctl list-interfaces {intf_path}").split('\n') + + expected_reg_sd_interfaces = [ + ("BootControl", "bootctl"), + ("Credentials", "creds"), + ("Hostname", "hostnamed"), + ("JournalAccess", "journald"), + ("Import", "importd"), + ("Machine", "machined"), + ("Resolve", "resolved-varlink"), + ("Resolve.Monitor", "resolved-monitor"), + ("Udev", "udevd-varlink"), + ("MuteConsole", "mute-console"), + ("FactoryReset", "factory-reset"), + ("AskPassword", "ask-password"), + ("Network", "networkd-varlink"), + ("Repart", "repart"), + ] + expected_priv_sd_interfaces = [ + ("Login", None), # systemd-logind-varlink.socket exist but is not necessary. + ] + expected_interfaces = [ + (f"io.systemd.{intf}", f"systemd-{socket_name}", f"/run/varlink/registry/io.systemd.{intf}") for intf, socket_name in expected_reg_sd_interfaces + ] + [ + (f"io.systemd.{intf}", f"systemd-{socket_name}" if socket_name is not None else None, f"/run/systemd/io.systemd.{intf}") for intf, socket_name in expected_priv_sd_interfaces + ] + + for intf, socket_name, intf_path in expected_interfaces: + if socket_name is not None: + machine.wait_for_unit(f"{socket_name}.socket") + assert intf in list_interfaces(intf_path), f"Interface '{intf}' not found in the Varlink registry" + ''; +} From d1700adc89d1ae68efe880a8a88f6735c3872680 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 25 Jun 2026 10:33:33 +0200 Subject: [PATCH 19/29] nano: remove pname substitution anti-pattern --- pkgs/by-name/na/nano/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/by-name/na/nano/package.nix b/pkgs/by-name/na/nano/package.nix index 2ab07429d9e6..2e68584027ee 100644 --- a/pkgs/by-name/na/nano/package.nix +++ b/pkgs/by-name/na/nano/package.nix @@ -34,7 +34,7 @@ stdenv.mkDerivation rec { version = "9.1"; src = fetchurl { - url = "mirror://gnu/nano/${pname}-${version}.tar.xz"; + url = "mirror://gnu/nano/nano-${version}.tar.xz"; hash = "sha256-X0d2QnTLdTI0nOCqIOwQ8ejoUabp+j62aBLEPRltsEI="; }; From b4a5a3d28f06bb80b99ab5b54382a83d3232fe87 Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Thu, 25 Jun 2026 09:53:18 +0200 Subject: [PATCH 20/29] nixos/virtualisation/nspawn-container: add virtualisation.credentials support Assisted-by: Claude --- .../virtualisation/credentials-options.nix | 63 +++++++++++++ .../nspawn-container/default.nix | 7 +- nixos/modules/virtualisation/qemu-vm.nix | 88 +++---------------- 3 files changed, 83 insertions(+), 75 deletions(-) create mode 100644 nixos/modules/virtualisation/credentials-options.nix diff --git a/nixos/modules/virtualisation/credentials-options.nix b/nixos/modules/virtualisation/credentials-options.nix new file mode 100644 index 000000000000..3345f6933784 --- /dev/null +++ b/nixos/modules/virtualisation/credentials-options.nix @@ -0,0 +1,63 @@ +{ lib, pkgs, ... }: +{ + options.virtualisation.credentials = lib.mkOption { + description = '' + Credentials to pass to the VM or container using systemd's credential system. + + See {manpage}`systemd.exec(5)`, {manpage}`systemd-creds(1)` and https://systemd.io/CREDENTIALS/ for more + information about systemd credentials. + ''; + default = { }; + example = lib.literalExpression '' + { + database-password = { + text = "my-secret-password"; + }; + ssl-cert = { + source = "./cert.pem"; + }; + binary-key = { + source = "./private.der"; + }; + } + ''; + type = lib.types.attrsOf ( + lib.types.submodule ( + { + name, + options, + config, + ... + }: + { + options = { + source = lib.mkOption { + type = lib.types.nullOr (lib.types.pathWith { }); + default = null; + description = '' + Source file on the host containing the credential data. + ''; + }; + text = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; + description = '' + Text content of the credential. + + For binary data or when the credential content should come from + an existing file, use `source` instead. + + ::: {.warning} + The text here is stored in the host's nix store as a file. + ::: + ''; + }; + }; + config.source = lib.mkIf (config.text != null) ( + lib.mkDerivedConfig options.text (pkgs.writeText name) + ); + } + ) + ); + }; +} diff --git a/nixos/modules/virtualisation/nspawn-container/default.nix b/nixos/modules/virtualisation/nspawn-container/default.nix index 9100b580768b..82a06929e925 100644 --- a/nixos/modules/virtualisation/nspawn-container/default.nix +++ b/nixos/modules/virtualisation/nspawn-container/default.nix @@ -21,6 +21,10 @@ let cfg = config.virtualisation; in { + imports = [ + ../credentials-options.nix + ]; + options = { virtualisation.cmdline = lib.mkOption { @@ -131,7 +135,8 @@ in # Send a READY=1 notification to a socket when the container is fully booted. "--notify-ready=yes" - ]; + ] + ++ lib.mapAttrsToList (name: cred: "--load-credential=${name}:${cred.source}") cfg.credentials; system.build.nspawn = let diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 8d2817c23bf0..8230da280b50 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -399,6 +399,7 @@ in imports = [ ../profiles/qemu-guest.nix ./disk-size-option.nix + ./credentials-options.nix (mkRenamedOptionModule [ "virtualisation" @@ -1126,81 +1127,20 @@ in }; virtualisation.credentials = mkOption { - description = '' - Credentials to pass to the VM using systemd's credential system. - - See {manpage}`systemd.exec(5)` , {manpage}`systemd-creds(1)` and https://systemd.io/CREDENTIALS/ for more - information about systemd credentials. - ''; - default = { }; - example = { - database-password = { - text = "my-secret-password"; - }; - ssl-cert = { - source = "./cert.pem"; - }; - binary-key = { - mechanism = "fw_cfg"; - source = "./private.der"; - }; - config-file = { - mechanism = "smbios"; - text = '' - [database] - host=localhost - port=5432 - ''; - }; - }; type = types.attrsOf ( - lib.types.submodule ( - { - name, - options, - config, - ... - }: - { - options = { - mechanism = lib.mkOption { - type = lib.types.enum [ - "fw_cfg" - "smbios" - ]; - default = if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"; - defaultText = lib.literalExpression ''if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"''; - description = '' - The mechanism used to pass the credential to the VM. - ''; - }; - source = lib.mkOption { - type = lib.types.nullOr (lib.types.pathWith { }); - default = null; - description = '' - Source file on the host containing the credential data. - ''; - }; - text = lib.mkOption { - default = null; - type = lib.types.nullOr lib.types.str; - description = '' - Text content of the credential. - - For binary data or when the credential content should come from - an existing file, use `source` instead. - - ::: {.warning} - The text here is stored in the host's nix store as a file. - ::: - ''; - }; - }; - config.source = lib.mkIf (config.text != null) ( - lib.mkDerivedConfig options.text (pkgs.writeText name) - ); - } - ) + lib.types.submodule { + options.mechanism = lib.mkOption { + type = lib.types.enum [ + "fw_cfg" + "smbios" + ]; + default = if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"; + defaultText = lib.literalExpression ''if pkgs.stdenv.hostPlatform.isx86 then "smbios" else "fw_cfg"''; + description = '' + The mechanism used to pass the credential to the VM. + ''; + }; + } ); }; From 612ff7f23c5bfffde6f1b6200941aafde22c9974 Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Thu, 25 Jun 2026 09:53:18 +0200 Subject: [PATCH 21/29] nixos/test-driver: support state_dir for container tests Assisted-by: Claude --- nixos/lib/test-driver/src/test_driver/machine/__init__.py | 1 + 1 file changed, 1 insertion(+) 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..b4eceee8c6fc 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -1684,6 +1684,7 @@ class NspawnMachine(BaseMachine): self.process = subprocess.Popen( [self.start_command], + cwd=self.state_dir, env={ "RUN_NSPAWN_ROOT_DIR": str(self.state_dir), "RUN_NSPAWN_SHARED_DIR": str(self.shared_dir), From 426413b862563b20afbc22ea9de00929942b77c0 Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Thu, 25 Jun 2026 09:53:18 +0200 Subject: [PATCH 22/29] nixos/tests/credentials: test both vm and container support Assisted-by: Claude --- nixos/tests/all-tests.nix | 16 ++--- nixos/tests/credentials.nix | 106 ++++++++++++++++++++++++++++ nixos/tests/qemu-vm-credentials.nix | 83 ---------------------- 3 files changed, 114 insertions(+), 91 deletions(-) create mode 100644 nixos/tests/credentials.nix delete mode 100644 nixos/tests/qemu-vm-credentials.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 09b0a4632d28..0e386407529f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -446,6 +446,14 @@ in }; coturn = runTest ./coturn.nix; couchdb = runTest ./couchdb.nix; + credentials-fwcfg = runTest { + imports = [ ./credentials.nix ]; + _module.args.mechanism = "fw_cfg"; + }; + credentials-smbios = runTestOn [ "x86_64-linux" ] { + imports = [ ./credentials.nix ]; + _module.args.mechanism = "smbios"; + }; cri-o = runTestOn [ "aarch64-linux" "x86_64-linux" ] ./cri-o.nix; croc = runTest ./croc.nix; cross-seed = runTest ./cross-seed.nix; @@ -1415,14 +1423,6 @@ in pykms = runTest ./pykms.nix; qbittorrent = runTest ./qbittorrent.nix; qboot = runTestOn [ "x86_64-linux" "i686-linux" ] ./qboot.nix; - qemu-vm-credentials-fwcfg = runTest { - imports = [ ./qemu-vm-credentials.nix ]; - _module.args.mechanism = "fw_cfg"; - }; - qemu-vm-credentials-smbios = runTestOn [ "x86_64-linux" ] { - imports = [ ./qemu-vm-credentials.nix ]; - _module.args.mechanism = "smbios"; - }; qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix; qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix { }; qemu-vm-store = runTest ./qemu-vm-store.nix; diff --git a/nixos/tests/credentials.nix b/nixos/tests/credentials.nix new file mode 100644 index 000000000000..7984ffd03593 --- /dev/null +++ b/nixos/tests/credentials.nix @@ -0,0 +1,106 @@ +{ + lib, + pkgs, + mechanism, + ... +}: + +let + secret = '' + foo + bar + baz + ''; + secret-file = "bar"; + + common-credentials = { + secret-default-mechanism = { + text = "default-mechanism"; + }; + secret-file-nix-store = { + source = pkgs.writeText "secret-file-nix-store" secret-file; + }; + secret-file-host = { + source = "./secret-file-host"; + }; + secret-file-host-binary = { + source = "./secret-file-host-binary"; + }; + }; +in + +{ + name = "credentials-${mechanism}"; + + meta.maintainers = with lib.maintainers; [ arianvp ]; + + # No VM<->container traffic in this test; credentials are static. + requiredFeatures.devnet = lib.mkForce false; + + nodes.vm = { + virtualisation.credentials = common-credentials // { + secret = { + inherit mechanism; + text = secret; + }; + }; + }; + + containers.container = { + virtualisation.credentials = common-credentials // { + secret = { + text = secret; + }; + }; + }; + + testScript = '' + import base64 + + secret_file_host = "baz" + # Binary data with null bytes, high bytes, and other problematic characters. + secret_file_host_binary = bytes([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0xDE, 0xAD, 0xBE, 0xEF, + 0xFF, 0xFE, 0xFD, 0xFC, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x81, 0x82, 0x83, + ]) + + def assert_credentials(m): + with open(m.state_dir / "secret-file-host", "w") as f: + f.write(secret_file_host) + with open(m.state_dir / "secret-file-host-binary", "wb") as f2: + f2.write(secret_file_host_binary) + + t.assertEqual( + m.succeed("systemd-creds --system cat secret").strip(), + "foo\nbar\nbaz", + ) + t.assertEqual( + m.succeed("systemd-creds --system cat secret-default-mechanism").strip(), + "default-mechanism", + ) + t.assertEqual( + m.succeed("systemd-creds --system cat secret-file-nix-store").strip(), + "${secret-file}", + ) + t.assertEqual( + m.succeed("systemd-creds --system cat secret-file-host").strip(), + secret_file_host, + ) + result = m.succeed( + "systemd-creds --system cat secret-file-host-binary --transcode=base64" + ).strip() + expected = base64.b64encode(secret_file_host_binary).decode("ascii") + t.assertEqual( + result, + expected, + f"Binary credential mismatch: got {result}, expected {expected}", + ) + + assert_credentials(vm) + assert_credentials(container) + ''; +} diff --git a/nixos/tests/qemu-vm-credentials.nix b/nixos/tests/qemu-vm-credentials.nix deleted file mode 100644 index 9ee2d270d8f0..000000000000 --- a/nixos/tests/qemu-vm-credentials.nix +++ /dev/null @@ -1,83 +0,0 @@ -{ - lib, - pkgs, - mechanism, - ... -}: - -let - secret = '' - foo - bar - baz - ''; - secret-file = "bar"; -in - -{ - name = "qemu-vm-credentials-${mechanism}"; - - meta.maintainers = with lib.maintainers; [ arianvp ]; - - nodes = { - machine = { - virtualisation.credentials = { - secret = { - inherit mechanism; - text = secret; - }; - secret-default-mechanism = { - text = "default-mechanism"; - }; - secret-file-nix-store = { - inherit mechanism; - source = pkgs.writeText "secret-file-nix-store" secret-file; - }; - secret-file-host = { - inherit mechanism; - source = "./secret-file-host"; - }; - secret-file-host-binary = { - inherit mechanism; - source = "./secret-file-host-binary"; - }; - }; - }; - }; - - testScript = '' - import base64 - secret_file_host = "baz" - # Binary data with null bytes, high bytes, and all sorts of problematic characters - secret_file_host_binary = bytes([ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, # null and control chars - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0xDE, 0xAD, 0xBE, 0xEF, # classic binary pattern - 0xFF, 0xFE, 0xFD, 0xFC, # high bytes - 0x00, 0x00, 0x00, 0x00, # multiple nulls - 0x80, 0x81, 0x82, 0x83, # more high bytes - ]) - - with open(machine.state_dir / "secret-file-host", "w") as f: - f.write(secret_file_host) - with open(machine.state_dir / "secret-file-host-binary", "wb") as f2: - f2.write(secret_file_host_binary) - - - # Test text credential - t.assertEqual(machine.succeed("systemd-creds --system cat secret").strip(), "foo\nbar\nbaz") - - t.assertEqual(machine.succeed("systemd-creds --system cat secret-default-mechanism").strip(), "default-mechanism") - - # Test credential from nix store - t.assertEqual(machine.succeed("systemd-creds --system cat secret-file-nix-store").strip(), "${secret-file}") - - # Test credential from host file - t.assertEqual(machine.succeed("systemd-creds --system cat secret-file-host").strip(), secret_file_host) - - # Test binary credential - verify exact binary content - result = machine.succeed("systemd-creds --system cat secret-file-host-binary --transcode=base64").strip() - expected = base64.b64encode(secret_file_host_binary).decode('ascii') - t.assertEqual(result, expected, f"Binary credential mismatch: got {result}, expected {expected}") - ''; -} From 9f4a8949cc99584f1e62185d7c5fa39cded7bd78 Mon Sep 17 00:00:00 2001 From: Jost Alemann Date: Fri, 26 Jun 2026 00:19:12 +0200 Subject: [PATCH 23/29] ruff: 0.15.19 -> 0.15.20 Changelog: https://github.com/astral-sh/ruff/releases/tag/0.15.20 Diff: https://github.com/astral-sh/ruff/compare/0.15.19...0.15.20 --- 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 86e800f61978..56982ab4db18 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.19"; + version = "0.15.20"; __structuredAttrs = true; @@ -24,12 +24,12 @@ rustPlatform.buildRustPackage (finalAttrs: { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-L0QRzc9S7vHOkk3WsfT0kgjBKbApsCHTgqCv4ts1j9g="; + hash = "sha256-8PFMGKG15kWBpG4YXg37940WtSe/e5pQDqIe3iJRh5A="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-DapI+Wv4KNlbGUCRIiAybyKNYipRIFd5O2fYy9Uatjg="; + cargoHash = "sha256-Bf6nsUnNMYapP0YN0SBkTPoP1czmj35tPwN1awyKhUw="; nativeBuildInputs = [ installShellFiles ]; From 89cfaf3eefe841d2f70161006613695e62a63b39 Mon Sep 17 00:00:00 2001 From: Jost Alemann Date: Fri, 26 Jun 2026 00:36:58 +0200 Subject: [PATCH 24/29] ty: 0.0.53 -> 0.0.54 Changelog: https://github.com/astral-sh/ty/releases/tag/0.0.54 Diff: https://github.com/astral-sh/ty/compare/0.0.53...0.0.54 --- pkgs/by-name/ty/ty/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ty/ty/package.nix b/pkgs/by-name/ty/ty/package.nix index ca6f502ade0f..187daf164c61 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.53"; + version = "0.0.54"; __structuredAttrs = true; src = fetchFromGitHub { @@ -25,7 +25,7 @@ rustPlatform.buildRustPackage (finalAttrs: { repo = "ty"; tag = finalAttrs.version; fetchSubmodules = true; - hash = "sha256-0msHGoPm2b7OX0Lax/yr2Uism1RxsH4DxhxlQP/aFpU="; + hash = "sha256-hbVH0dCUHkWKD9IG/CYhYI4TfLgpk++tPOkCD36eVSg="; }; # For Darwin platforms, remove the integration test for file notifications, @@ -39,7 +39,7 @@ rustPlatform.buildRustPackage (finalAttrs: { cargoBuildFlags = [ "--package=ty" ]; - cargoHash = "sha256-DapI+Wv4KNlbGUCRIiAybyKNYipRIFd5O2fYy9Uatjg="; + cargoHash = "sha256-Bf6nsUnNMYapP0YN0SBkTPoP1czmj35tPwN1awyKhUw="; nativeBuildInputs = [ installShellFiles ]; buildInputs = [ rust-jemalloc-sys ]; From 03aae1016e762e4c25bb5b158d3bebbb90a56d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Santos=20Reis?= Date: Tue, 23 Jun 2026 15:05:12 +0100 Subject: [PATCH 25/29] busybox: backport tar symlink bug fix --- pkgs/os-specific/linux/busybox/default.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/os-specific/linux/busybox/default.nix b/pkgs/os-specific/linux/busybox/default.nix index d155c1064676..bfb3f424aa45 100644 --- a/pkgs/os-specific/linux/busybox/default.nix +++ b/pkgs/os-specific/linux/busybox/default.nix @@ -99,6 +99,13 @@ stdenv.mkDerivation (finalAttrs: { excludes = [ "networking/httpd_ratelimit_cgi.c" ]; # New since release. hash = "sha256-Msm9sDZrVx7ofunnvnTS73SPKUUpR3Tv5xZ/wBd+rts="; }) + # tar: only strip unsafe components from hardlinks, not symlinks + # fix issue introduced by the previous patch (CVE-2026-26157_CVE-2026-26158.patch) + (fetchpatch { + name = "CVE-2026-26157_CVE-2026-26158-2.patch"; + url = "https://github.com/vda-linux/busybox_mirror/commit/599f5dd8fac390c18b79cba4c14c334957605dae.patch"; + hash = "sha256-go/KHSsuMSm21nC0yvKEtAQs8Jnjjqdcs5i8RWBGwT4="; + }) # syslogd: fix writing to local log file # https://lists.busybox.net/pipermail/busybox/2024-October/090969.html (fetchpatch { From dccad3b9c424fe87bde2248fcc1cf2239eb7e7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Santos=20Reis?= Date: Fri, 26 Jun 2026 10:40:11 +0100 Subject: [PATCH 26/29] busybox: re-enable tar tests --- pkgs/os-specific/linux/busybox/default.nix | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkgs/os-specific/linux/busybox/default.nix b/pkgs/os-specific/linux/busybox/default.nix index bfb3f424aa45..20a76523092a 100644 --- a/pkgs/os-specific/linux/busybox/default.nix +++ b/pkgs/os-specific/linux/busybox/default.nix @@ -270,12 +270,6 @@ stdenv.mkDerivation (finalAttrs: { # Relies on suid/guid bits skip-testcase cpio.tests "cpio restores suid/sgid bits" - # Weird failures, looks related to our sandbox - skip-testcase tar.tests "tar does not extract into symlinks" - skip-testcase tar.tests "tar -k does not extract into symlinks" - skip-testcase tar.tests "tar Symlink attack: create symlink and then write through it" - skip-testcase tar.tests "tar Symlinks and hardlinks coexist" - popd ''; }); From 0899fedf44910c9dd259a937e744a69a3e96d874 Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:37:47 +0000 Subject: [PATCH 27/29] linux_7_1: 7.1.1 -> 7.1.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 54ff5434cfe3..323533422ed5 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 }, "7.1": { - "version": "7.1.1", - "hash": "sha256:0z8x6wafxzc5vkim9jh8wpycdkk9y5bpxgsirmdpyznw84szl5aj", + "version": "7.1.2", + "hash": "sha256:0gw8nnq6nix9xk2dhb1jwmhnqjayrn3bn2akzg4lgqkvfa9qq69p", "lts": false } } From ae05d822afa333fd35d417b399beb1da0ebb689d Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:38:00 +0000 Subject: [PATCH 28/29] linux_7_0: 7.0.13 -> 7.0.14 --- 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 323533422ed5..651594b7683f 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 }, "7.0": { - "version": "7.0.13", - "hash": "sha256:04wrz38ldls7pv1yxa1m7p2hqn1731l93xnz93fs7b0nyz8fv09w", + "version": "7.0.14", + "hash": "sha256:160ggaq9rh50a39gz02fpia8maq85bwxhqlwsc03yafjhjvrk6fy", "lts": false }, "7.1": { From 77ea4e37f84a559c32e38391066198989e9f1b32 Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:38:13 +0000 Subject: [PATCH 29/29] linux_6_18: 6.18.36 -> 6.18.37 --- 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 651594b7683f..1850e03a312b 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.36", - "hash": "sha256:0kn4r43lnd5nb5c298b30030qyaxv05s7k40n9si1j3iyk4qdazv", + "version": "6.18.37", + "hash": "sha256:0maj2ap1m09bxl6a3g9wc65h9sdr6y8rwc5qcqlbavb4wq0d4g58", "lts": true }, "7.0": {