From d5fe4cc48f6e21bd460267d304bda63e2e284976 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Mon, 16 Feb 2026 03:51:52 +0000 Subject: [PATCH 01/55] python3Packages.cvxopt: 1.3.2 -> 1.3.3 --- pkgs/development/python-modules/cvxopt/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/development/python-modules/cvxopt/default.nix b/pkgs/development/python-modules/cvxopt/default.nix index 94dbff6ab5fa..6e1fd821ef09 100644 --- a/pkgs/development/python-modules/cvxopt/default.nix +++ b/pkgs/development/python-modules/cvxopt/default.nix @@ -20,14 +20,14 @@ assert (!blas.isILP64) && (!lapack.isILP64); buildPythonPackage rec { pname = "cvxopt"; - version = "1.3.2"; + version = "1.3.3"; format = "setuptools"; disabled = isPyPy; # hangs at [translation:info] src = fetchPypi { inherit pname version; - hash = "sha256-NGH6QsGyJAuk2h2YXKc1A5FBV/xMd0FzJ+1tfYWs2+Y="; + hash = "sha256-gFnO9B8fEVyHvJt1/sn4bblefwr88DpS1hm6Qz5EO8s="; }; buildInputs = [ From 69db1ea8fd99accd68069a6e332bfa4285269d46 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sun, 24 May 2026 13:21:44 +0200 Subject: [PATCH 02/55] doc: init styleguide This is heavily inspired by: - https://git.clan.lol/clan/clan-core/src/commit/dc08dbc73053990489be8855f0314707f5ab1534/docs/src/guides/contributing/styleguide.md Written by Jeff Cogswell, author of countless CPP-for-dummies books Distilled from these sources: - [Google Developer Documentation Style Guide](https://developers.google.com/style) (CC BY 4.0) - [Microsoft Writing Style Guide](https://learn.microsoft.com/en-us/style-guide/) - [Diataxis](https://diataxis.fr/) (CC BY-SA 4.0) - [developer-docs-framework](https://github.com/anivar/developer-docs-framework) (MIT) --- doc/README.md | 17 +- doc/styleguide.md | 420 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 428 insertions(+), 9 deletions(-) create mode 100644 doc/styleguide.md diff --git a/doc/README.md b/doc/README.md index abb07f6ff3dd..4fafb9f575b8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -207,6 +207,8 @@ When needed, each convention explains why it exists, so you can make a decision Note that these conventions are about the **structure** of the manual (and its source files), not about the content that goes in it. You, as the writer of documentation, are still in charge of its content. +**For prose style, see the [documentation styleguide](./styleguide.md).** + ### One sentence per line Put each sentence in its own line. @@ -219,17 +221,16 @@ When changing existing content, update formatting if possible, but avoid excessi ### Examples first -Readers look at examples first: an example communicates what something does faster than a description. -Put examples before detailed explanations. +Put examples before detailed explanations (see the [styleguide](./styleguide.md) for the rationale). -Prefer this structure for each documented item: +Use this structure for each documented item: 1. Title -2. Abstract (optional, one sentence max, the example often speaks for itself) +2. Abstract (optional, one sentence max) 3. Example 4. Explanation (details, edge cases, types, defaults) -For instance: +Rendered example: ````markdown ## `lib.toUpper` @@ -281,11 +282,9 @@ Returns the difference as a number. Use the [admonition syntax](#admonitions) for callouts and examples. -### Provide self-contained examples +### `callPackage`-compatible examples -Provide at least one example per function, and make examples self-contained. -This is easier to understand for beginners. -It also helps with testing that it actually works – especially once we introduce automation. +Provide at least one example per function. Example code should be such that it can be passed to `pkgs.callPackage`. Instead of something like: diff --git a/doc/styleguide.md b/doc/styleguide.md new file mode 100644 index 000000000000..e1d12c2d9419 --- /dev/null +++ b/doc/styleguide.md @@ -0,0 +1,420 @@ +# Styleguide + +## Writing Principles + +A consistent style greatly increases the usability of all documentation and communication. + +Use this page as a reference and style guide for our internal and external documentation. + +### Knowledge Expectations + +**Assume competence, not familiarity.** + +Write for someone who knows a great deal — up to but not including this project. + +**What readers know:** + +- Basic computer operation +- Command line familiarity +- General interest in systems configuration + +**What readers don't know:** + +- NixOS-specific concepts +- NixOS ecosystem details or grammar +- NixOS workflows + +If specific knowledge is required, mention it at the start of the page. + +#### Show, Don't Tell + +The fastest path to understanding is a working example. +People learn by doing, not by reading about doing. + +**Recommended structure:** + +- Start with the minimal working code or command +- Briefly explain what it does +- Cover edge cases or variations +- Link to further information instead of including it + +#### Grammar and Style + +**Sentence structure:** + +- Use simple, direct sentences +- Break complex ideas into multiple short sentences +- Avoid nested clauses + +**Bad:** + +> The following command, which utilizes nixos-generate-config to produce a comprehensive hardware configuration, will write the results back into the respective configuration directory located on your local machine. + +What the user does is hidden in the middle. +`nixos-generate-config` is a leaked implementation detail. +Users care about *detecting hardware*, not *the tool that does it*. + +**Good:** + +> This command detects your hardware and saves the configuration. + +#### Content Organization + +Lead with value. State what the reader will accomplish before explaining how. + +**Bad:** + +> To create a new NixOS configuration that you can later use as a webserver, first navigate to your project directory, then add a new host configuration file with the desired machine name. + +**Good:** + +Add a webserver configuration to your NixOS setup: + +```nix +# hosts/webserver/configuration.nix +{ ... }: +{ + services.nginx.enable = true; +} +``` + +Use **progressive disclosure**. Introduce concepts only when needed. + +**Recommended structure:** + +1. State the goal (one sentence) +2. Show the simplest working example +3. Explain concepts if needed +4. Provide advanced options separately or link to the reference + +#### No Meta-commentary + +Don't describe what the documentation does. Just do it. + +**Don't:** + +> This section explains how to configure networking. +> The following guide walks you through setting up a web server. + +**Do:** + +> Configure networking by setting: +> Set up a web server: + +#### Code Examples + +**Keep examples focused:** + +- Show one concept at a time +- Use realistic but simple scenarios +- Avoid dependencies on other examples + +**Minimal comments** + +Let the code speak for itself. +Paste code examples directly and without further alteration. + +**Bad:** + +```nix +# This sets the hostname for the machine +{ + networking.hostName = "webserver"; # Change this to your machine's hostname + # This enables SSH access + services.openssh.enable = true; # Required for remote deployment +} +``` + +**Good:** + +```nix +{ + networking.hostName = "webserver"; + services.openssh.enable = true; +} +``` + +#### Lead with Practical Examples + +Don't front-load theory. Readers want to accomplish something first, then understand why it works. + +- Show configuration as *what you want*, not *how the module system works* +- Introduce Nix-specific concepts only when they are needed to complete the task +- Defer language mechanics to reference pages or `nix.dev` + +**Bad:** + +> Before adding a service, you need to understand the NixOS module system and attribute set merging. + +**Good:** + +Enable nginx: + +```nix +{ services.nginx.enable = true; } +``` + +This adds nginx to your system configuration. Rebuild to apply: + +```bash +sudo nixos-rebuild switch +``` + +#### Teach Nix through examples, not theory + + +Users learn the NixOS module system by seeing patterns first. + +- Start with a working example +- Explanation follows the code +- Link deeper concepts instead of inlining them +- Link to `nix.dev` for optional learning + +#### General Rules + +- Abbreviate keys like `ssh-ed25519 AAAAC3NzaC…` +- Abbreviate IP addresses like `192.168.XXX.XXX` +- Variables are capitalized and start with `$`, e.g. `$YOUR_HOSTNAME` +- Variables should be directly usable during copy-paste +- Do **not** describe missing code parts (`#elided`, `#omitted`) +- **Machine vs Host**: use "machine" for the NixOS system identity, "host" for the physical or virtual hardware + +#### Capitalization + +- GB / RAM / HDD +- bootable USB drive +- Wi-Fi / DHCP / DNS +- macOS / NixOS / Nix / Linux +- Flakes +- git + +#### Headings + +Use sentence case. A reader scanning only headings should understand the page. + +**Don't:** + +> Getting Started +> Overview +> Configure The Database + +**Do:** + +> Set up a PostgreSQL database +> Configure networking +> Add a user to the system + +#### Imperative Mood, Voice, and Person + +Use imperative mood for instructions. Address the reader as "you", not "the user". Use active voice; in other words, make the subject do the action. + +**Don't:** + +> The user should run the following command. +> The configuration will need to be updated. +> The key is generated by the system. + +**Do:** + +> Run the command. +> Update the configuration. +> The system generates the key. + +#### Tense + +Use present tense for descriptions. Future tense makes documentation feel tentative. + +**Don't:** + +> This will create a new folder. +> Running this command will install the package. + +**Do:** + +> This creates a new folder. +> Running this command installs the package. + +#### Be Confident + +State facts. Don't hedge with "should," "might," "typically," or "usually" unless the behavior genuinely varies. + +**Don't:** + +> This should create the configuration file. +> The service will usually start automatically. + +**Do:** + +> This creates the configuration file. +> The service starts automatically. + +#### Avoid Nominalizations + +A nominalization is a verb turned into a noun, often by adding *-tion*, *-meant*, or *-ance* (e.g. "explanation", "selection"). The fix: find the hidden verb and use it directly. + +**Don't:** + +> Make a selection from the list. +> Provide an explanation of the error. + +**Do:** + +> Select from the list. +> Explain the error. + +#### Plain Words + +Technical precision for technical terms; plain language for everything else. + +- "use" not "utilize" +- "start" not "initiate" +- "end" not "terminate" +- "help" not "facilitate" +- "send" not "transmit" +- "set up" not "establish" +- "find out" not "ascertain" + +#### Filler Words and Weak Phrases + +Cut words and phrases that add length without meaning. + +Delete on sight: + +- "simply", "just", "easily", "basically", "obviously" +- "in order to" → use "to" +- "allows you to" → use the verb directly +- "it's worth noting that" → just say the thing +- no exclamation marks in technical prose + +**Don't:** + +> Simply run `nixos-rebuild switch`. +> In order to deploy, you first need to run the command, which allows you to push the config. +> It's worth noting that this requires root access. + +**Do:** + +> Run `nixos-rebuild switch`. +> To deploy, run: +> This requires root access. + +Every word must earn its place. + +#### Writing Procedures + +One instruction per sentence. Don't pack multiple actions into one sentence. + +**Don't:** + +> Navigate to your project directory and run the command, then check the output. + +**Do:** + +1. Navigate to your project directory. +2. Run the command. +3. Check the output. + +Don't bury the negative. Key limitations should be prominent, not a footnote after a positive description. + +**Don't:** + +> This service supports multiple roles, integrates with existing modules, and works great for most setups (note that multiple instances are not supported). + +**Do:** + +> This service does not support multiple instances. + +#### Consistent Terminology + +Pick a term and stick to it. Don't swap synonyms to avoid repetition. In technical documentation, repetition is clarity. + +**Don't:** + +> Create a machine... configure the host... deploy the node. + +**Do:** + +> Create a machine... configure the machine... deploy the machine. + +#### Links + +Use descriptive link text. Never use "click here" or "this link." + +**Don't:** + +> For more information, see `[this page](url)`. +> Click `[here](url)` to read the reference. + +**Do:** + +> See the `[NixOS options reference](url)` for details. +> Read the `[NixOS module system guide](url)`. + +Only link when the destination is directly relevant, not for generic background context (sometimes known as "Wikipedia-style links"). Readers feel obligated to click links, fearing they'll miss something important. Don't send them to a generic article about a technology when they're looking for how *your* system uses it. + +**Don't:** + +> Our software uses [SQLite](https://sqlite.org/) for storage. +> *(Reader clicks expecting schema details — finds a generic product page instead.)* + +(Note that in the above example, the SQLite link is the SQLite home page, which is likely not pertinent.) + +**Do:** + +> See `[database schema](url)` for the full table structure. + +#### UI Language + +Match UI element names exactly: wording, casing, and spacing (even if a label seems oddly worded). + +**Don't:** + +> Click the generator button. +> Select the save option. + +**Do:** + +> Click **Generate a Key**. +> Click **Save Changes**. + +Someone will go looking for a button labeled "generator." They will not find it. They will be frustrated. + +Consistency between documentation and interface builds confidence. Words are part of the interface. + +:::{.tip} +This can be tricky as UI changes; we don't yet have a policy in place for how to handle this. We welcome comments and suggestions. +::: + +#### Clean system discipline + +Your machine has things new users don't: cached credentials, installed tools, environment variables, existing configuration. When writing or updating documentation: + +**Don't:** + +> Write steps from memory on your development machine, assuming what works there will work everywhere. + +**Do:** + +> - Start on a clean system — a fresh VM or new user account +> - Take notes in real time as you work through the steps +> - Document every warning, prompt, or unexpected output the system shows + +Also think in combinations: WSL vs native Linux, with and without existing keys. You don't need to test every matrix square — but you need to know which ones diverge. + +#### Never type code — always copy-paste + +Always copy commands and code from a terminal where you just ran them successfully. Never retype from memory. + +**Don't:** + +> Retype a command from memory into the documentation. +> Retype code into a code-block from memory + +**Do:** + +> Paste commands directly from the shell or IDE. +> Paste code that has been successfully validated with nix-instantiate or nix-build + +Replace sensitive values with placeholders: ``, ``, ``. + +Typed-from-memory commands introduce subtle errors. Even the most experienced software developers have occasional typos. From c33058f7c67b4a11ecda11d97078c1e539d73858 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Thu, 30 Apr 2026 20:10:42 -0400 Subject: [PATCH 03/55] maddy: 0.8.2 -> 0.9.4 Diff: https://github.com/foxcpp/maddy/compare/v0.8.2...v0.9.4 --- pkgs/by-name/ma/maddy/package.nix | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pkgs/by-name/ma/maddy/package.nix b/pkgs/by-name/ma/maddy/package.nix index e8a523ecd06e..21a391e5a717 100644 --- a/pkgs/by-name/ma/maddy/package.nix +++ b/pkgs/by-name/ma/maddy/package.nix @@ -12,16 +12,16 @@ buildGoModule (finalAttrs: { pname = "maddy"; - version = "0.8.2"; + version = "0.9.4"; src = fetchFromGitHub { owner = "foxcpp"; repo = "maddy"; rev = "v${finalAttrs.version}"; - sha256 = "sha256-+tj2h1rAdr0SPgLGGzVf5sdFmhcwY76fkMm2P/gYFuo="; + sha256 = "sha256-U7czabdpOC+vb5ERFbbS5W4h7pOCwbEZuXbU/MXRvW4="; }; - vendorHash = "sha256-+xsG7z2wSxoZ1vEJIDBtwDMiU7zKCtZOsYPUhv6HMpQ="; + vendorHash = "sha256-8dMS2kFlQ762u4Ifv1O1Capr8Jb7wsQuHSsJvHwa0j0="; tags = [ "libpam" ]; @@ -29,9 +29,14 @@ buildGoModule (finalAttrs: { "-s" "-w" "-X github.com/foxcpp/maddy.Version=${finalAttrs.version}" + "-X github.com/foxcpp/maddy.DefaultLibexecDirectory=/run/wrappers/bin" ]; - subPackages = [ "cmd/maddy" ]; + subPackages = [ + "cmd/maddy" + "cmd/maddy-pam-helper" + "cmd/maddy-shadow-helper" + ]; buildInputs = [ pam ]; @@ -49,15 +54,18 @@ buildGoModule (finalAttrs: { ln -s "$out/bin/maddy" "$out/bin/maddyctl" + mkdir -p "$out/libexec/maddy" + mv "$out/bin/maddy-pam-helper" "$out/bin/maddy-shadow-helper" "$out/libexec/maddy" + mkdir -p $out/lib/systemd/system substitute dist/systemd/maddy.service $out/lib/systemd/system/maddy.service \ - --replace "/usr/local/bin/maddy" "$out/bin/maddy" \ - --replace "/bin/kill" "${coreutils}/bin/kill" + --replace-fail "/usr/local/bin/maddy" "$out/bin/maddy" \ + --replace-fail "/bin/kill" "${coreutils}/bin/kill" substitute dist/systemd/maddy@.service $out/lib/systemd/system/maddy@.service \ - --replace "/usr/local/bin/maddy" "$out/bin/maddy" \ - --replace "/bin/kill" "${coreutils}/bin/kill" + --replace-fail "/usr/local/bin/maddy" "$out/bin/maddy" \ + --replace-fail "/bin/kill" "${coreutils}/bin/kill" ''; env.NIX_CFLAGS_COMPILE = lib.optionalString stdenv.cc.isClang "-Wno-error=strict-prototypes"; @@ -69,5 +77,6 @@ buildGoModule (finalAttrs: { homepage = "https://maddy.email"; license = lib.licenses.gpl3Plus; maintainers = with lib.maintainers; [ nickcao ]; + mainProgram = "maddy"; }; }) From f2abedd11a7353624f40740d412ec8cbdf9ef91d Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Mon, 25 May 2026 08:21:00 -0400 Subject: [PATCH 04/55] maddy: 0.9.4 -> 0.9.5 Diff: https://github.com/foxcpp/maddy/compare/v0.9.4...v0.9.5 --- pkgs/by-name/ma/maddy/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/ma/maddy/package.nix b/pkgs/by-name/ma/maddy/package.nix index 21a391e5a717..6221bbf256dd 100644 --- a/pkgs/by-name/ma/maddy/package.nix +++ b/pkgs/by-name/ma/maddy/package.nix @@ -12,13 +12,13 @@ buildGoModule (finalAttrs: { pname = "maddy"; - version = "0.9.4"; + version = "0.9.5"; src = fetchFromGitHub { owner = "foxcpp"; repo = "maddy"; rev = "v${finalAttrs.version}"; - sha256 = "sha256-U7czabdpOC+vb5ERFbbS5W4h7pOCwbEZuXbU/MXRvW4="; + sha256 = "sha256-Lt5uj7DCu6Tx47Xdzg+CjGN543LCj2x8ph+1wvD3GCQ="; }; vendorHash = "sha256-8dMS2kFlQ762u4Ifv1O1Capr8Jb7wsQuHSsJvHwa0j0="; From 84160ddeb87005ca31b58ea0c55c9c7241d1eb77 Mon Sep 17 00:00:00 2001 From: ArisoN Date: Mon, 25 May 2026 15:49:50 +0300 Subject: [PATCH 05/55] nixos/firewalld: add reload triggers for config file changes When firewalld serves as the backend for networking.firewall, changes to allowedTCPPorts, zones, settings etc. rewrite /etc/firewalld/* but firewalld.service was never reloaded. Add reloadTriggers pointing to firewalld.conf, all zone XMLs and service XMLs so switch-to-configuration reloads the daemon on nixos-rebuild switch. nixos/firewalld: treefmt --- .../services/networking/firewalld/default.nix | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/firewalld/default.nix b/nixos/modules/services/networking/firewalld/default.nix index e0e8bf4cb47c..aa6217db21ab 100644 --- a/nixos/modules/services/networking/firewalld/default.nix +++ b/nixos/modules/services/networking/firewalld/default.nix @@ -57,7 +57,19 @@ in systemd.services.firewalld = { aliases = [ "dbus-org.fedoraproject.FirewallD1.service" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecReload = "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID"; + serviceConfig.ExecReload = [ + "" + "${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID" + ]; + reloadTriggers = [ + config.environment.etc."firewalld/firewalld.conf".source + ] + ++ lib.mapAttrsToList ( + name: _: config.environment.etc."firewalld/zones/${name}.xml".source + ) config.services.firewalld.zones + ++ lib.mapAttrsToList ( + name: _: config.environment.etc."firewalld/services/${name}.xml".source + ) config.services.firewalld.services; environment.NIX_FIREWALLD_CONFIG_PATH = "${paths}/lib/firewalld"; }; }; From ee0805035fe5eac7b8b0b19d6a0e560fafe6ceb9 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 26 May 2026 22:52:27 +0000 Subject: [PATCH 06/55] rdma-core: add static platforms to `badPlatforms` rdma makes use of `dlopen` internally to load a "provider" plugin based on the nic (intel, broadcom, etc). that's fundamentally incompatible with static builds. --- pkgs/by-name/rd/rdma-core/package.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/by-name/rd/rdma-core/package.nix b/pkgs/by-name/rd/rdma-core/package.nix index 138d03fa2133..fa6d99f4039a 100644 --- a/pkgs/by-name/rd/rdma-core/package.nix +++ b/pkgs/by-name/rd/rdma-core/package.nix @@ -78,6 +78,7 @@ stdenv.mkDerivation (finalAttrs: { homepage = "https://github.com/linux-rdma/rdma-core"; license = lib.licenses.gpl2Only; platforms = lib.platforms.linux; + badPlatforms = [ lib.systems.inspect.platformPatterns.isStatic ]; maintainers = [ lib.maintainers.markuskowa ]; }; }) From 5105500b167d2f6754cfa01dddc6aa060a4c9316 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Fri, 29 May 2026 16:10:30 +0000 Subject: [PATCH 07/55] spire: 1.15.0 -> 1.15.1 --- pkgs/by-name/sp/spire/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/sp/spire/package.nix b/pkgs/by-name/sp/spire/package.nix index 61622afeb917..b99163dc7d3a 100644 --- a/pkgs/by-name/sp/spire/package.nix +++ b/pkgs/by-name/sp/spire/package.nix @@ -8,7 +8,7 @@ buildGoModule (finalAttrs: { pname = "spire"; - version = "1.15.0"; + version = "1.15.1"; outputs = [ "out" @@ -21,12 +21,12 @@ buildGoModule (finalAttrs: { owner = "spiffe"; repo = "spire"; tag = "v${finalAttrs.version}"; - sha256 = "sha256-mOYPVvumMIXgfJGQeStU62DlBOIwI0Sav5cQonU28r0="; + sha256 = "sha256-7SmHj/st2r3ks8Bh6gVRlKoay5mHqpovH25qMxG9s40="; }; # Needed for github.co/google/go-tpm-tools/simulator which contains non-go files that `go mod vendor` strips proxyVendor = true; - vendorHash = "sha256-F9M7Tvo/yF1QVnjB7Gp3mbZc7159Dx7wgttjstkA/1w="; + vendorHash = "sha256-wKVBqjid/PQi5JBB37c3h68Q8kUqbyaiDbLssO7Yo7A="; buildInputs = [ openssl ]; From 08a2d5fff737305a13c39a47a49cf8590567220d Mon Sep 17 00:00:00 2001 From: K900 Date: Fri, 29 May 2026 12:11:37 +0300 Subject: [PATCH 08/55] vscode: 1.119.0 -> 1.122.1 --- pkgs/applications/editors/vscode/generic.nix | 34 +++++++++++++++++--- pkgs/applications/editors/vscode/vscode.nix | 16 ++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/pkgs/applications/editors/vscode/generic.nix b/pkgs/applications/editors/vscode/generic.nix index 7f1b627c5ef0..72fd06c5870b 100644 --- a/pkgs/applications/editors/vscode/generic.nix +++ b/pkgs/applications/editors/vscode/generic.nix @@ -338,8 +338,10 @@ stdenv.mkDerivation ( # Remove native encryption code, as it derives the key from the executable path which does not work for us. # The credentials should be stored in a secure keychain already, so the benefit of this is questionable # in the first place. + # Also remove prebuilt Copilot binaries that seemingly have been added by accident. + '' rm -rf $out/lib/${libraryName}/resources/app/node_modules/vscode-encrypt + rm -rf $out/lib/${libraryName}/resources/app/node_modules/@github/copilot-linuxmusl* '' ) + '' @@ -407,14 +409,38 @@ stdenv.mkDerivation ( ) + ( let - vscodeRipgrep = + nodeModulesPath = if stdenv.hostPlatform.isDarwin then if lib.versionAtLeast vscodeVersion "1.94.0" then - "Contents/Resources/app/node_modules/@vscode/ripgrep/bin/rg" + "Contents/Resources/app/node_modules" else - "Contents/Resources/app/node_modules.asar.unpacked/@vscode/ripgrep/bin/rg" + "Contents/Resources/app/node_modules.asar.unpacked" else - "resources/app/node_modules/@vscode/ripgrep/bin/rg"; + "resources/app/node_modules"; + + # see https://www.npmjs.com/package/@vscode/ripgrep-universal?activeTab=code + ripgrepSystem = + { + x86_64-darwin = "darwin-x64"; + aarch64-darwin = "darwin-arm64"; + armv7l-linux = "linux-arm"; + aarch64-linux = "linux-arm64"; + i686-linux = "linux-ia32"; + powerpc64-linux = "linux-ppc64"; + riscv64-linux = "linux-riscv64"; + s390x-linux = "linux-s390x"; + x86_64-linux = "linux-x64"; + } + .${stdenv.hostPlatform.system} + or (throw "Unknown system for ripgrep-universal: ${stdenv.hostPlatform.system}"); + + ripgrepPath = + if lib.versionAtLeast vscodeVersion "1.122.0" then + "@vscode/ripgrep-universal/bin/${ripgrepSystem}/rg" + else + "@vscode/ripgrep/bin/rg"; + + vscodeRipgrep = "${nodeModulesPath}/${ripgrepPath}"; in if !useVSCodeRipgrep then '' diff --git a/pkgs/applications/editors/vscode/vscode.nix b/pkgs/applications/editors/vscode/vscode.nix index bb091c071025..d3685fdf55f0 100644 --- a/pkgs/applications/editors/vscode/vscode.nix +++ b/pkgs/applications/editors/vscode/vscode.nix @@ -35,17 +35,17 @@ let hash = { - x86_64-linux = "sha256-HcZIRGB0y8U5huxXN9jNrhMD0Jjmn+QNUU60EHGduXo="; - x86_64-darwin = "sha256-mMDxEAt/Lst4ifeczcL+QT8mVVXNk8fDNTM1YHGZ8tY="; - aarch64-linux = "sha256-o0JV1Vc6utTmJkH9uTSylBsYM3mAfiDIgwg3LUOBWb0="; - aarch64-darwin = "sha256-8ixVOUe4EcNX/z0jnux1hXOhnG1JuhbssH2BARqU80o="; - armv7l-linux = "sha256-KxrSOVCdfa4L9RlnHybwGLRciMFwC/COsctX+5nqR/c="; + x86_64-linux = "sha256-t26YN3E5XaSJ7gki8nm06hVh4ZvXDEU77M749ZrqfAo="; + x86_64-darwin = "sha256-jOnwhiDJmU+EqU30wg1+frqDDxJgfngETx414i2YTIg="; + aarch64-linux = "sha256-8sYanI12qDMPgVG7S0QKLEkU0i/SICkJ5wz/OwhP+i4="; + aarch64-darwin = "sha256-oXeZZWAvpUn5KItEOR8yX9iQ0Fp6EzXGux0jvYbZqtU="; + armv7l-linux = "sha256-16cUu1C389edf0aHxXxTLJwjxmpHxM8mv1YFnPDLgP4="; } .${system} or throwSystem; # Please backport all compatible updates to the stable release. # This is important for the extension ecosystem. - version = "1.119.0"; + version = "1.122.1"; # The update server (update.code.visualstudio.com) expects the version path # segment in X.Y.Z form, so we normalize X.Y to X.Y.0 (e.g. "1.110" → "1.110.0"). @@ -53,7 +53,7 @@ let downloadVersion = lib.versions.pad 3 version; # This is used for VS Code - Remote SSH test - rev = "8b640eef5a6c6089c029249d48efa5c99adf7d51"; + rev = "8761a5560cfd65fdd19ce7e2bd18dab5c0a4d84e"; in buildVscode { pname = "vscode" + lib.optionalString isInsiders "-insiders"; @@ -86,7 +86,7 @@ buildVscode { src = fetchurl { name = "vscode-server-${rev}.tar.gz"; url = "https://update.code.visualstudio.com/commit:${rev}/server-linux-x64/stable"; - hash = "sha256-FyRpbjxY8PWr8z+ttn1H93ud4raFAJz704Vn38+LYCM="; + hash = "sha256-7n8KvIYEDYO8qqJjfbuUsgUwCxq9FJ6i/EuDBd1HQDk="; }; stdenv = stdenvNoCC; }; From 02190b58d4f43b519ab51857d55264eb4938e97e Mon Sep 17 00:00:00 2001 From: Daniel Nagy Date: Sun, 31 May 2026 10:30:00 +0200 Subject: [PATCH 09/55] chickenPackages.chickenEggs: update --- .../compilers/chicken/5/default.nix | 2 + .../development/compilers/chicken/5/deps.toml | 199 ++++++++++-------- .../compilers/chicken/5/overrides.nix | 1 + 3 files changed, 113 insertions(+), 89 deletions(-) diff --git a/pkgs/development/compilers/chicken/5/default.nix b/pkgs/development/compilers/chicken/5/default.nix index 650a05042981..2cfcc1c3cf03 100644 --- a/pkgs/development/compilers/chicken/5/default.nix +++ b/pkgs/development/compilers/chicken/5/default.nix @@ -9,6 +9,8 @@ let # these invalid dependencies. invalidDependencies = [ "srfi-4" + "cond-expand" + "http-curl" ]; in lib.makeScope newScope (self: { diff --git a/pkgs/development/compilers/chicken/5/deps.toml b/pkgs/development/compilers/chicken/5/deps.toml index 8652d50b20a4..5a6d3d7ff2ec 100644 --- a/pkgs/development/compilers/chicken/5/deps.toml +++ b/pkgs/development/compilers/chicken/5/deps.toml @@ -345,9 +345,9 @@ version = "2.13.20191214-0" [box] dependencies = [] license = "bsd" -sha256 = "1nnnb28bkmv3hc19jhvc4mvd56y6wqxd9izwhwx4g9f7jgk9xamb" +sha256 = "0pmdnj4rp2fvd02yjcsf4l81k41yab3xhij3yhxqnzh3sbmz46gi" synopsis = "Boxing" -version = "3.9.0" +version = "3.9.1" [breadcrumbs] dependencies = ["srfi-1"] @@ -359,16 +359,16 @@ version = "1.2" [breadline] dependencies = ["apropos", "srfi-18"] license = "gpl-3" -sha256 = "1rvffygravnaw5sns03qfn28zznvamprfhmzgscjfpck1j4x6ylc" +sha256 = "02x0f26rdaypw21wisz0c38j08pp18rrh77bxxxqyysrc2fws7xl" synopsis = "Bindings to readline" -version = "1.0.2" +version = "1.0.3" [brev-separate] dependencies = ["matchable", "miscmacros", "srfi-1", "srfi-69"] license = "bsd-1-clause" -sha256 = "1ak9n8rdccnkhg8bnin7j4wqqj15wjp7r1mjc7z9627zas9jsj8w" +sha256 = "1p9vl0s79jird242ngsfcwng7lpanvk12x50gqzl4s41kqh5569g" synopsis = "Hodge podge of macros and combinators" -version = "1.99" +version = "1.100" [brev] dependencies = ["anaphora", "brev-separate", "clojurian", "combinators", "define-options", "dwim-sort", "fix-me-now", "acetone", "html-parser", "match-generics", "http-client", "matchable", "miscmacros", "scsh-process", "sequences", "srfi-1", "srfi-42", "srfi-69", "strse", "sxml-serializer", "sxml-transforms", "sxpath", "tree", "uri-common"] @@ -429,9 +429,9 @@ version = "0.3.5" [char-set-literals] dependencies = ["srfi-14"] license = "bsd" -sha256 = "1im25d5wvw7c913k5rq7axq457mg6z4yg64mk74g95x5r177x45i" +sha256 = "08hq0j302kv2z26hmbvljip8cw4s6wbrmh8a4a53g68fpbcprbbw" synopsis = "A reader extension providing Gauche style literals for SRFI-14 char-sets" -version = "0.4" +version = "0.5" [check-errors] dependencies = [] @@ -492,9 +492,9 @@ version = "0.3.1" [chicken-doc] dependencies = ["matchable", "fmt", "sxml-transforms", "srfi-1", "srfi-13", "srfi-69"] license = "bsd" -sha256 = "1p7i5dsi9x8kfchh3xdw9ww9pz2p861v8vynqzwmbclpqjrspllh" +sha256 = "1shad3vwpy5nrnzq95xi4vjh3b1vbmxsp0qsv5bgy6npbs7p0sx7" synopsis = "Explore Chicken documentation locally" -version = "0.7.0" +version = "0.7.1" [chicken-update] dependencies = ["srfi-1", "http-client", "uri-common", "getopt-long"] @@ -590,9 +590,9 @@ version = "1.23" [commands] dependencies = [] license = "bsd" -sha256 = "13y49vrkd9rs98s9fk10g8w056xb9nnqxwn1372sayw64789i3ib" +sha256 = "1gxhsw7bwakwds18yd2j3653fdh63xwpi25jnzwshvmzw57himsq" synopsis = "Helpers for programs that dispatch commands" -version = "1.0.0" +version = "1.0.1" [comparse] dependencies = ["lazy-seq", "trie", "matchable", "srfi-1", "srfi-13", "srfi-14", "srfi-69"] @@ -639,9 +639,9 @@ version = "2.3.2" [coops] dependencies = ["matchable", "miscmacros", "record-variants", "srfi-1"] license = "bsd" -sha256 = "1gzrjb4dj66c9nca6yychbamcslgip0qy2hzfk2v0a41251kl1q5" +sha256 = "0b255m8h3xf0av4zjy1vkfxfdrwlrlzpyds0l4v0c5j7jwxzcka5" synopsis = "A featureful object system" -version = "1.4" +version = "1.5" [crc] dependencies = [] @@ -1063,6 +1063,13 @@ sha256 = "1ywgjrhkr45837xf5vnb2i3aacby7yjkhm62drdf70c09za860dd" synopsis = "Filesystems in Userspace" version = "0.1.1" +[fusion-arrays] +dependencies = ["datatype", "matchable", "random-mtzig", "srfi-69"] +license = "gpl-3" +sha256 = "0cycj2fhpgn50yjgy6f8jacq4g9b3nknmnv7rfb25f4c14xx2c7w" +synopsis = "Fusion arrays." +version = "1.0" + [futures] dependencies = ["srfi-18"] license = "bsd" @@ -1115,9 +1122,9 @@ version = "0.2.3" [generalized-arrays] dependencies = ["r7rs", "srfi-128", "srfi-133", "srfi-143", "srfi-160", "srfi-253", "transducers"] license = "bsd-3" -sha256 = "0j4spxk36llwxkwdlvb3ljr78v2bd1mknn3pcdw80mh4s2ampapw" +sha256 = "0h1bkgz4a8pp8sm6dc022v4ygzxfff8zhpk91bkrhj5vv6wvnnzc" synopsis = "Provides generalized arrays, intervals, and storage classes for CHICKEN Scheme." -version = "2.2.2" +version = "2.3.0" [generics] dependencies = ["simple-cells"] @@ -1147,6 +1154,13 @@ sha256 = "07q0c0d8lvsxly4bwifhwmm1cizz9nx5r8p9zbkb9vi6xfd5xfi3" synopsis = "Utilities for getopt-long" version = "1.2.1" +[ggplot] +dependencies = ["srfi-1", "datatype", "matchable", "yasos", "statistics", "cairo"] +license = "gpl-3" +sha256 = "1cmyz3qbc4l3pci34kjx7nnx0hb8va4hi9vsn1v2bmaidlflqdpn" +synopsis = "A declarative plotting system based on Grammar of Graphics." +version = "1.2" + [git] dependencies = ["srfi-69", "foreigners", "module-declarations", "srfi-1"] license = "bsd" @@ -1458,9 +1472,9 @@ version = "0.4" [ipfs] dependencies = ["http-client", "intarweb", "medea", "srfi-1", "srfi-13", "srfi-189", "srfi-197", "uri-common"] license = "unlicense" -sha256 = "07dknmklg035wr5yqqjnghv162dfdryr87xilrkrwpg88v1wzrs4" +sha256 = "0fyb1nja398fb7gisfy3j7nrhky424kjipakyhi0fy5yrhpgkfrb" synopsis = "IPFS HTTP API for Scheme" -version = "0.0.22" +version = "0.0.24" [irc] dependencies = ["matchable", "regex", "srfi-1"] @@ -1563,9 +1577,9 @@ version = "0.3" [lay] dependencies = [] license = "bsd" -sha256 = "09f0fd9gcv9yrl81vh9bfh1ywda9gljpa48w3vvmya0aknh1x05q" +sha256 = "0x924nms0wmn167vbay49zqbkfc7sxycjn3bkhvm3qv1ds738f8n" synopsis = "Lay eggs efficiently" -version = "0.3.4" +version = "0.4.0" [lazy-ffi] dependencies = ["bind", "srfi-1", "srfi-69"] @@ -1612,16 +1626,16 @@ version = "8.2" [libyaml] dependencies = ["varg"] license = "mit" -sha256 = "1wj0gmry0jj1ni50grkh96a56pc0fgal26djlzk6h0qxb9cb58k1" -synopsis = "A yaml parser based on libyaml" -version = "1.0.3" +sha256 = "1y1d8sps54iba6zi6lkmxvmxzl97m4cmw2jlg3r3hnpmf0px5ppb" +synopsis = "A yaml parser based on libfyaml" +version = "2.0.1" [linenoise] dependencies = [] license = "bsd" -sha256 = "0is9j6hq6nmnsrn2g01ssxg8hwndc3y6fx02hsvfdsqqzj8qzjvr" +sha256 = "0783nlbzcmznv7px1lvi65spfc6sgjykmm4h808zy8hq97vc74cp" synopsis = "A minimal, zero-config, BSD licensed, readline replacement." -version = "1.0" +version = "1.1" [list-comprehensions] dependencies = [] @@ -1633,9 +1647,9 @@ version = "1.2.1" [list-utils] dependencies = ["utf8", "srfi-1", "check-errors"] license = "bsd" -sha256 = "0bqfp9i10mrz5zs1mh2x7pgcp980qq9l31krxz37j619d1s5q807" +sha256 = "0n8b2mfpspn5nyh182n1n3h2x2wpiy3v7188pl8icsivfbsidli5" synopsis = "list-utils" -version = "2.8.0" +version = "2.10.2" [live-define] dependencies = ["matchable"] @@ -1654,9 +1668,9 @@ version = "1.2" [llm] dependencies = ["medea", "base64", "uri-common", "http-client", "intarweb", "openssl", "srfi-1", "srfi-13", "logger"] license = "bsd-3-clause" -sha256 = "15xf195jkjk1lkn6xkr455h3nkmvllpmwpcsl1b0xd5jdqnwly75" +sha256 = "0czgrk4mbz7drap7y27lign0ivx1jxp8q0cazgcwi2ahv6szlwxm" synopsis = "Provider-agnostic LLM chat API client with tool calling support" -version = "0.0.6" +version = "0.0.9" [llrb-syntax] dependencies = [] @@ -1703,9 +1717,9 @@ version = "1.0.1" [logger] dependencies = ["srfi-1", "medea"] license = "bsd-3-clause" -sha256 = "0k85w08p1b6r2gdrqcrgczc3qpg64rw49ch2i76apzdxl088fijm" +sha256 = "1g35cfmmgfwxawna7xf3p9k4a8ix6gy6z4q1424yd1b3jq0smdv2" synopsis = "Simple structured logging with per-module level control" -version = "0.0.2" +version = "0.0.6" [loop] dependencies = ["srfi-1"] @@ -1721,6 +1735,13 @@ sha256 = "0ihnsnjr2mfac2z053ra5167791c3jzz11wvj1fz2jz688pdr6rg" synopsis = "A pure Chicken Markdown parser" version = "3" +[lru-cache] +dependencies = ["srfi-69", "matchable"] +license = "lgpl-3.0-or-later" +sha256 = "0njhx57p269k4xwmmf6z27dz0npkmynb1lckrvr6nmk02cf3yink" +synopsis = "Least recently used (LRU) cache" +version = "0.1.0" + [lsp-server] dependencies = ["apropos", "chicken-doc", "json-rpc", "nrepl", "r7rs", "srfi-1", "srfi-18", "srfi-69", "srfi-133", "srfi-180", "uri-generic", "utf8"] license = "mit" @@ -1801,9 +1822,9 @@ version = "1.2" [math-utils] dependencies = ["memoize", "miscmacros", "srfi-1", "vector-lib"] license = "public-domain" -sha256 = "0k5gb9jmcjyf29icdyf81956x70df15fxmb0rb0vz3ysl5spw49q" +sha256 = "0nz990k27g2dizd1l5xqpsfbzggy8i76dgb3lma0pyfzijj0gdjf" synopsis = "Miscellaneous math utilities" -version = "1.14.1" +version = "1.15.0" [math] dependencies = ["srfi-1", "r6rs-bytevectors", "miscmacros", "srfi-133", "srfi-42"] @@ -1920,9 +1941,9 @@ version = "0.3.4" [micro-stats] dependencies = ["srfi-1", "sequences", "sequences-utils"] license = "gplv3" -sha256 = "1xwf5jqrbsl8xsnp497lzxc8vdabc98za9lmbnwpi2kyaz4ncw3w" +sha256 = "1x8gzr8bk0m8pxiyxbnyxpszgd9rksjkij7gkq7scxs8dq3y4liv" synopsis = "Easily create micro-stats" -version = "0.4.1" +version = "0.6.0" [mini-kanren] dependencies = [] @@ -2060,9 +2081,9 @@ version = "2.0" [nitrate] dependencies = ["r7rs", "srfi-225"] license = "bsd-0-clause" -sha256 = "0jf95nh4z4b6rrh337v27d7zvpmzcr3r7731cg41vdnxh5w6sji3" +sha256 = "0n63rhnp6vwrqfi80gqhr1k03ky4xlsx0gzk796pc0296cszbzql" synopsis = "Combinator based pattern matching" -version = "1.0.0" +version = "2.0.0" [noise] dependencies = ["glls"] @@ -2100,11 +2121,11 @@ synopsis = "OAuth 1.0, 1.0a, RFC 5849" version = "0.3" [oauthtoothy] -dependencies = ["schematra", "openssl", "http-client", "intarweb", "uri-common", "medea", "schematra", "schematra-session"] +dependencies = ["schematra", "openssl", "http-curl", "intarweb", "uri-common", "medea", "schematra", "schematra-session"] license = "bsd" -sha256 = "0i88m3v20w5wjij9m10596aw9a447jhxnl511fkkq6341wvgdgyj" +sha256 = "04ly7wsbsy79q3n2xkgbsv73clw78yyk98vb77ddnl0r9ssm4vr1" synopsis = "Oauth2 support for Schematra" -version = "0.2.3" +version = "0.3.1" [object-evict] dependencies = ["srfi-69"] @@ -2263,9 +2284,9 @@ version = "1.1" [posix-regex] dependencies = ["r7rs"] license = "gpl-3" -sha256 = "1zswh1d96q24271mkzx7fc8802z9h9bkcb8nmakpf8prl1d2yjvg" +sha256 = "0ds5i7whn3ivxajc3rhqwnifh1iv4v6gmqakq5aq53x3ngsiz81f" synopsis = "A thin wrapper around POSIX regular expression matching" -version = "0.1.0" +version = "0.1.3" [posix-shm] dependencies = ["srfi-1", "compile-file"] @@ -2403,9 +2424,9 @@ version = "1.0" [quasiwalk] dependencies = ["matchable", "brev-separate"] license = "bsd-1-clause" -sha256 = "1w9lvcqdipsmxzpg8ka7y2yy46vklbzad9lzkml873ijfn23bw27" +sha256 = "1qackl0nvy93kmrcmz6qpidpjlc2lsi39ps74p1kz8byy10dc7lh" synopsis = "A tree map that respects quote, quasiquote, and unquote" -version = "1.10" +version = "1.12" [queues] dependencies = [] @@ -2459,9 +2480,9 @@ version = "5.1" [raylib] dependencies = ["foreigners"] license = "mit" -sha256 = "1d9lx18525iya1lmlrfr43vjn40wiyyy819cfpa1a8z7dxz7lhiq" +sha256 = "11waq027rpiyxr8h42d51gc0gscscfrva0c237v2gjd5j312bm22" synopsis = "Bindings for raylib: A simple and easy-to-use library to enjoy videogames programming" -version = "1.1.1" +version = "1.3.0" [rb-tree] dependencies = ["datatype", "matchable", "yasos", "srfi-1"] @@ -2499,11 +2520,11 @@ synopsis = "Procedural record-type interface" version = "1.4" [redis] -dependencies = ["r7rs", "srfi-34", "srfi-35", "srfi-69", "srfi-99", "srfi-113", "srfi-128", "srfi-133", "srfi-152", "srfi-158"] +dependencies = ["srfi-34", "srfi-35", "srfi-69", "srfi-113", "srfi-128", "srfi-152", "srfi-158", "cond-expand"] license = "bsd" -sha256 = "1p3q9216y0ddnghcy83h3xm0vi2qg17kv1v1xff2sfz4mzliy6qf" +sha256 = "00r0j9dk0ldpjpskk8qm321g46rn0fn6q2i1in38iqn3502pvr1d" synopsis = "A Redis client library for Chicken Scheme" -version = "0.6" +version = "0.9" [regex-case] dependencies = ["regex"] @@ -2669,16 +2690,16 @@ version = "0.1.1" [schematra-session] dependencies = ["message-digest", "hmac", "sha2", "base64", "srfi-69", "schematra"] license = "bsd" -sha256 = "1kq0s713cysj03c0n1piknam3aghvkvjxvphj3r39hgm84ygdm9q" +sha256 = "0qa3iy3zblfvvgfsbj7b8k8x94kyi084bys31lh4rmvs4f3xwky0" synopsis = "Session support for Schematra" -version = "0.1.0" +version = "0.1.3" [schematra] -dependencies = ["spiffy", "base64", "nrepl", "srfi-1", "srfi-13", "srfi-18", "srfi-69", "chiccup", "medea"] +dependencies = ["spiffy", "base64", "simple-sha1", "nrepl", "srfi-1", "srfi-13", "srfi-18", "srfi-69", "chiccup", "medea", "multipart-form-data", "logger"] license = "bsd" -sha256 = "0sdjdswgrh6fq5bf1793w6vnc12a1mn406xbiq1rfypf6xmb6077" +sha256 = "1jf9nafaa2zsmg6r55vslyh0glrp05yq0bja3vbhpxlmfrjdm10l" synopsis = "Schematra is a minimalistic web server built on top of spiffy" -version = "0.4.3" +version = "0.7.0" [scheme-indent] dependencies = ["srfi-1"] @@ -2935,9 +2956,9 @@ version = "1.1.4" [slib-charplot] dependencies = ["utf8", "slib-arraymap", "srfi-63", "slib-compat"] license = "artistic" -sha256 = "1ms4akx76m56ny0p42gzx8nk3fip24kkxg928s9317w7k7nqidih" +sha256 = "0b7hwfn00sml3dshm8m34kb2vzl6kxyiiv4gsm7qaafnj8mrb6hn" synopsis = "The SLIB character plotting library" -version = "1.3.1" +version = "1.3.3" [slib-compat] dependencies = ["srfi-1"] @@ -3173,9 +3194,9 @@ version = "0.2.1" [srfi-127] dependencies = [] license = "bsd" -sha256 = "0gjzz7q7frzd97i9skxragznm3as1423nng3kmgpgk1wmi4jmsi5" +sha256 = "0cpwkf490ljz51z706c57c67y6fq5saxc9srz1p63rj5i714i9cd" synopsis = "SRFI-127: Lazy Sequences" -version = "1.3" +version = "1.4" [srfi-128] dependencies = ["srfi-13"] @@ -3292,9 +3313,9 @@ version = "1.0" [srfi-158] dependencies = [] license = "mit" -sha256 = "02ayjw2rd8p0iiw97z2fvbxaq3v0x48afwsdcc9gahdl5bwvg2qd" +sha256 = "1v90i460a20czmc0b3wwdmqq99z1a9iwc993bfwff4fc07yy9blj" synopsis = "SRFI 158: Generators and Accumulators" -version = "0.1" +version = "1.0" [srfi-160] dependencies = ["srfi-128"] @@ -3348,9 +3369,9 @@ version = "0.1.7" [srfi-180] dependencies = ["srfi-34", "srfi-35", "srfi-158"] license = "bsd" -sha256 = "14y763q74hrk9lgb21bdymcjinfd9bvf2q4kqm7vzay7ayghhc3x" +sha256 = "0qx9fj8cijcm6lsch6wqq5l90rw28k8wc97sc2rz50c2z272hnpr" synopsis = "A JSON parser and printer that supports JSON bigger than memory." -version = "1.6.1" +version = "1.6.2" [srfi-189] dependencies = ["srfi-1", "typed-records"] @@ -3362,9 +3383,9 @@ version = "1.0.3" [srfi-19] dependencies = ["srfi-1", "utf8", "srfi-18", "srfi-29", "miscmacros", "locale", "record-variants", "check-errors"] license = "bsd" -sha256 = "07dbqmj40w2p4jja8jl7icv1i626vv1sm5m8r4iql5g2jaby8n8a" +sha256 = "1ajz2x8ffx5g1gz6qf87v9lmv23i4zs2wkq4gjnpwby2ix3k4sqc" synopsis = "Time Data Types and Procedures" -version = "4.13.0" +version = "4.13.1" [srfi-193] dependencies = [] @@ -3439,9 +3460,9 @@ version = "0.1" [srfi-217] dependencies = ["srfi-1", "srfi-143", "typed-records"] license = "mit" -sha256 = "0ynasgp03kqd6nhqmcnp4cjf87p3pkjaqi2x860hma79xsslyp8n" +sha256 = "1z5fz1sqn7p37ynfc37hssf1n9c0w88yhlwyf2kzrnhygis3f12r" synopsis = "SRFI 217: Integer Sets" -version = "0.2" +version = "0.3" [srfi-225] dependencies = ["r7rs", "srfi-1", "srfi-128", "srfi-69", "srfi-146"] @@ -3493,18 +3514,18 @@ synopsis = "SRFI 253: Data (Type-)Checking" version = "0.2.0" [srfi-259] -dependencies = ["r7rs", "integer-map"] +dependencies = ["r7rs"] license = "mit" -sha256 = "1sf41jkmmakq7l194fn3zjy6hr0bjx37z3ysiym9za6hg199fnq7" +sha256 = "0gw7agvxzribc1fmw2qcp6xkym5ph10s1yacx2xf95q415bm8jy5" synopsis = "Tagged procedures with type safety (with SRFI-229 compatability)" -version = "1.1.1" +version = "2.0.0" [srfi-27] dependencies = ["srfi-1", "vector-lib", "timed-resource", "miscmacros", "check-errors"] license = "bsd" -sha256 = "1bybfg0hv0a1gxb7mdq0q88zdnsmz9fdm27mnnhkhhmy9wh7y49x" +sha256 = "0ilrj25qv52y7bwgaswhnz6smpfd3gdrkq5i0zpqy0wjxb5ziqbr" synopsis = "Sources of Random Bits" -version = "4.2.4" +version = "4.3.1" [srfi-29] dependencies = ["srfi-1", "srfi-69", "utf8", "locale", "posix-utils", "condition-utils", "check-errors"] @@ -3822,11 +3843,11 @@ synopsis = "Parse svnwiki to sxml" version = "0.2.14" [svnwiki2html] -dependencies = ["qwiki", "svnwiki-sxml", "sxml-transforms", "srfi-1", "srfi-13"] +dependencies = ["qwiki", "svnwiki-sxml", "sxml-transforms"] license = "bsd" -sha256 = "1bb9w4j7imhr24hp037sk6p0b2mla0fmfcvqdmi5ji5jwzvn9ir0" +sha256 = "132g33b7340wjb6r190jq42y3vxrhr5qkpl8xsg8f8zzmdymjz2i" synopsis = "A program to convert svnwiki syntax to HTML" -version = "0.0.3" +version = "0.1.1" [sxml-modifications] dependencies = ["srfi-1", "sxpath"] @@ -3866,9 +3887,9 @@ version = "2.8.0" [synch] dependencies = ["srfi-18", "check-errors"] license = "bsd" -sha256 = "09vf7ljkpiiaib8wslpjnabhqw70l6z5aqkp3nx223nqh4qgr8mb" +sha256 = "1s1vqkma9qambbnbf9bcwa8c1whs1sswal6fj7mwa3nxdqpy5wdb" synopsis = "Synchronization Forms" -version = "3.3.9" +version = "3.3.13" [sysexits] dependencies = [] @@ -3943,9 +3964,9 @@ version = "1.0.5" [test-utils] dependencies = ["test"] license = "bsd" -sha256 = "0fbhxs81s5y4sfinkipqymhxcw8z5p25ffy9cw9dzf3g82a4gv1w" +sha256 = "1yl7pjyvz55ih1gv2zkzhv0fp9jjwas3akmyx3zncvqaqcwp38hk" synopsis = "Test Utilities (for test egg)" -version = "1.6.1" +version = "1.7.0" [test] dependencies = [] @@ -4018,11 +4039,11 @@ synopsis = "tracing and breakpoints" version = "2.0" [transducers] -dependencies = ["r7rs", "srfi-128", "srfi-143", "srfi-146", "srfi-160", "srfi-253"] +dependencies = ["r7rs", "srfi-128", "srfi-143", "srfi-160", "srfi-253"] license = "mit" -sha256 = "00phm1lsc0k3fxcyvfjvdpfjxrcl67djm3794p8bdx7jd3gvhgln" +sha256 = "1621bw4y122dvl0xh6pswna3c793gaqwwb9d4iswjfzs8xcz8f4n" synopsis = "Transducers for working with foldable data types." -version = "0.8.0" +version = "0.10.0" [transmission] dependencies = ["http-client", "intarweb", "medea", "r7rs", "srfi-1", "srfi-189", "uri-common"] @@ -4153,9 +4174,9 @@ version = "1.0" [utf8] dependencies = ["srfi-69", "iset", "regex"] license = "bsd" -sha256 = "0vpf2vw4k7b1f7afgminnvkgiq9m973rxi1w2y63jgg8vr7i027p" +sha256 = "1054744fvfzgd1fpzjwb11h2cbmpff5khfh37a987c7d7hh4106s" synopsis = "Unicode support" -version = "3.6.3" +version = "3.6.4" [uuid-lib] dependencies = ["record-variants"] @@ -4181,9 +4202,9 @@ version = "0.15" [varg] dependencies = [] license = "mit" -sha256 = "1v9j09q76prls2vq8s6yfrwzvw50fb1mi6id8dp1kc6jfkiqi2qg" +sha256 = "0lig5acswmfmhfxin6j0jyg77rksjnla6fccwx93xicw5mhbj3ws" synopsis = "A template for defining dynamic arguments procedure" -version = "1.0.2" +version = "1.1.2" [vector-lib] dependencies = [] @@ -4291,11 +4312,11 @@ synopsis = "A gzip (RFC1952) compression and decompression library" version = "2.0" [zlib] -dependencies = ["foreigners", "miscmacros"] -license = "gpl-3" -sha256 = "0m3kkspj2l4ssl82gx058h747xmbhm66cxc1z0908b0pjkw5byx7" +dependencies = [] +license = "zlib" +sha256 = "125cg3nvm17v0iam4a79gldlxck64m5ih7wd1h9bnzx3zqcxxc6m" synopsis = "Bindings for zlib" -version = "0.7.0" +version = "0.8.0" [zmq] dependencies = ["srfi-1", "srfi-13", "srfi-18", "foreigners"] diff --git a/pkgs/development/compilers/chicken/5/overrides.nix b/pkgs/development/compilers/chicken/5/overrides.nix index 519446ea9959..90e483195687 100644 --- a/pkgs/development/compilers/chicken/5/overrides.nix +++ b/pkgs/development/compilers/chicken/5/overrides.nix @@ -331,6 +331,7 @@ in hypergiant = broken; iup = broken; kiwi = broken; + libyaml = broken; lmdb-ht = broken; mpi = broken; oauthtoothy = broken; From 7b3af176b4a8f6a3636ae462c0ecf30818e0c83e Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Sun, 31 May 2026 14:55:52 +0000 Subject: [PATCH 10/55] ngtcp2-gnutls: 1.22.1 -> 1.23.0 --- pkgs/development/libraries/ngtcp2/gnutls.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/development/libraries/ngtcp2/gnutls.nix b/pkgs/development/libraries/ngtcp2/gnutls.nix index a91e41e96bc5..d4aaec7a46f2 100644 --- a/pkgs/development/libraries/ngtcp2/gnutls.nix +++ b/pkgs/development/libraries/ngtcp2/gnutls.nix @@ -13,13 +13,13 @@ stdenv.mkDerivation rec { pname = "ngtcp2"; - version = "1.22.1"; + version = "1.23.0"; src = fetchFromGitHub { owner = "ngtcp2"; repo = "ngtcp2"; rev = "v${version}"; - hash = "sha256-QVxDBVOUVwqMuJZo+RbwIbVxWJSPQpbjbHvuCVPfYUs="; + hash = "sha256-mCqppkfqf6QWWmdnTidxH4vdaB3gpRYS200vme0GUjQ="; }; outputs = [ From 767cc29b719fb397b1aa43ddbf29457849f3e615 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Sun, 31 May 2026 23:34:25 +0000 Subject: [PATCH 11/55] rsshub: 0-unstable-2026-05-23 -> 0-unstable-2026-05-31 --- pkgs/by-name/rs/rsshub/package.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/by-name/rs/rsshub/package.nix b/pkgs/by-name/rs/rsshub/package.nix index bc45272d7f1e..353735aa181f 100644 --- a/pkgs/by-name/rs/rsshub/package.nix +++ b/pkgs/by-name/rs/rsshub/package.nix @@ -12,13 +12,13 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "rsshub"; - version = "0-unstable-2026-05-23"; + version = "0-unstable-2026-05-31"; src = fetchFromGitHub { owner = "DIYgod"; repo = "RSSHub"; - rev = "b5bcb8a5677b0491248e1f8dc732552d0198b8d0"; - hash = "sha256-IgjzdXCyetDOU74Oy2f+aGhaEbGqX3oI78Kp2CUi0YM="; + rev = "575f71abdeb94b3bb0a4fda3c3ae00d14f7715fd"; + hash = "sha256-GAGe6AMT0WPr1riBzz06IbJ5/O9GJ1RU3H+VeZF4Sj0="; }; patches = [ @@ -31,7 +31,7 @@ stdenv.mkDerivation (finalAttrs: { pnpmDeps = fetchPnpmDeps { inherit (finalAttrs) pname version src; fetcherVersion = 3; - hash = "sha256-KB9NAK0lqnMZ504hbKoj1Mpm0swDvrBSW4ggAORqKOo="; + hash = "sha256-ZdG4ZIYLYYUQToJHqI+GWDq2KiQrryMrUHtCn/qxr+o="; pnpm = pnpm_10; }; From a8058cdc4f509b65d7b962c3d2c694f856bdc04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=BCtz?= Date: Sun, 31 May 2026 16:57:43 -0700 Subject: [PATCH 12/55] gnumeric: 1.12.60 -> 1.12.61 Diff: https://gitlab.gnome.org/GNOME/gnumeric/-/compare/GNUMERIC_1_12_60...GNUMERIC_1_12_61 Changelog: https://gitlab.gnome.org/GNOME/gnumeric/-/blob/GNUMERIC_1_12_61/ChangeLog --- pkgs/by-name/gn/gnumeric/package.nix | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/pkgs/by-name/gn/gnumeric/package.nix b/pkgs/by-name/gn/gnumeric/package.nix index c970c80a1bf2..5aa2168a73b3 100644 --- a/pkgs/by-name/gn/gnumeric/package.nix +++ b/pkgs/by-name/gn/gnumeric/package.nix @@ -1,7 +1,6 @@ { lib, stdenv, - fetchpatch, pkg-config, intltool, libxml2, @@ -27,25 +26,16 @@ let in stdenv.mkDerivation (finalAttrs: { pname = "gnumeric"; - version = "1.12.60"; + version = "1.12.61"; src = fetchFromGitLab { domain = "gitlab.gnome.org"; owner = "GNOME"; repo = "gnumeric"; tag = "GNUMERIC_${lib.replaceStrings [ "." ] [ "_" ] finalAttrs.version}"; - hash = "sha256-fv4RlIfJiLY3MbsAsgRgJ010/Ob1X1be29XfoweCMpI="; + hash = "sha256-SrAFYLCYacTobOmb+Jk4f4OWVLcWS8aq8OBFrdwYcbE="; }; - patches = [ - # Replace bool type with gboolean. - # See https://gitlab.gnome.org/GNOME/gnumeric/-/merge_requests/39. - (fetchpatch { - url = "https://gitlab.gnome.org/GNOME/gnumeric/-/commit/dee6523426b75c10c36b188fafe6e7a27b6631e3.patch"; - hash = "sha256-a4KgxsrU9m/dZqu2LNC+jWiXvCTcRPzZW/67pg8yLGY="; - }) - ]; - postPatch = '' substituteInPlace configure.ac \ --replace-fail 'GLIB_COMPILE_RESOURCES=' 'GLIB_COMPILE_RESOURCES="glib-compile-resources"#' @@ -96,6 +86,7 @@ stdenv.mkDerivation (finalAttrs: { }; meta = { + changelog = "https://gitlab.gnome.org/GNOME/gnumeric/-/blob/${finalAttrs.src.tag}/ChangeLog"; description = "GNOME Office Spreadsheet"; license = lib.licenses.gpl2Plus; homepage = "http://projects.gnome.org/gnumeric/"; From d8de2d196084380b733c61f21729b9247d97d118 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Mon, 1 Jun 2026 00:33:25 +0000 Subject: [PATCH 13/55] shikane: 1.0.1 -> 1.1.0 --- pkgs/by-name/sh/shikane/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/sh/shikane/package.nix b/pkgs/by-name/sh/shikane/package.nix index 90e3577efbf1..a8cf49877916 100644 --- a/pkgs/by-name/sh/shikane/package.nix +++ b/pkgs/by-name/sh/shikane/package.nix @@ -8,16 +8,16 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "shikane"; - version = "1.0.1"; + version = "1.1.0"; src = fetchFromGitLab { owner = "w0lff"; repo = "shikane"; rev = "v${finalAttrs.version}"; - hash = "sha256-Chc1+JUHXzuLl26NuBGVxSiXiaE4Ns1FXb0dBs6STVk="; + hash = "sha256-YjHFhGP2A8dQTOmeeBqB2ij3Zgs0n/uuisvWTH8fyfQ="; }; - cargoHash = "sha256-eVEfuX/dNFoNH9o18fIx51DP/MWrQMqInU4wtGCmUbQ="; + cargoHash = "sha256-ajmEbE5Y4LkxvYRFE6aBDZxNpGULTmKeu6/k92kWjQg="; nativeBuildInputs = [ installShellFiles From 176b638426326e66a2c75a6296ae76972fa3af0e Mon Sep 17 00:00:00 2001 From: Vincent Laporte Date: Mon, 1 Jun 2026 07:30:09 +0200 Subject: [PATCH 14/55] ocamlPackages: remove legacy uses of dune_3 --- pkgs/development/ocaml-modules/chrome-trace/default.nix | 9 +++------ .../ocaml-modules/dune-action-plugin/default.nix | 8 +++----- .../ocaml-modules/dune-configurator/default.nix | 6 ++---- pkgs/development/ocaml-modules/dune-glob/default.nix | 8 +++----- .../ocaml-modules/dune-private-libs/default.nix | 8 ++------ pkgs/development/ocaml-modules/dune-rpc/default.nix | 8 +++----- pkgs/development/ocaml-modules/dune-site/default.nix | 8 +++----- pkgs/development/ocaml-modules/dyn/default.nix | 7 +++---- pkgs/development/ocaml-modules/fs-io/default.nix | 6 +++--- pkgs/development/ocaml-modules/top-closure/default.nix | 6 +++--- 10 files changed, 28 insertions(+), 46 deletions(-) diff --git a/pkgs/development/ocaml-modules/chrome-trace/default.nix b/pkgs/development/ocaml-modules/chrome-trace/default.nix index 8af8908bf69c..d7bfe130ee48 100644 --- a/pkgs/development/ocaml-modules/chrome-trace/default.nix +++ b/pkgs/development/ocaml-modules/chrome-trace/default.nix @@ -1,21 +1,18 @@ { lib, buildDunePackage, - dune_3, + dune, }: buildDunePackage { pname = "chrome-trace"; - inherit (dune_3) src version; - - minimalOCamlVersion = "4.08"; - duneVersion = "3"; + inherit (dune) src version; dontAddPrefix = true; meta = { description = "Chrome trace event generation library"; - inherit (dune_3.meta) homepage; + inherit (dune.meta) homepage; license = lib.licenses.mit; }; } diff --git a/pkgs/development/ocaml-modules/dune-action-plugin/default.nix b/pkgs/development/ocaml-modules/dune-action-plugin/default.nix index b85049782569..b1443df4357e 100644 --- a/pkgs/development/ocaml-modules/dune-action-plugin/default.nix +++ b/pkgs/development/ocaml-modules/dune-action-plugin/default.nix @@ -1,7 +1,7 @@ { lib, buildDunePackage, - dune_3, + dune, dune-glob, dune-private-libs, dune-rpc, @@ -9,9 +9,7 @@ buildDunePackage { pname = "dune-action-plugin"; - inherit (dune_3) src version; - - duneVersion = "3"; + inherit (dune) src version; dontAddPrefix = true; @@ -22,7 +20,7 @@ buildDunePackage { ]; meta = { - inherit (dune_3.meta) homepage; + inherit (dune.meta) homepage; description = "API for writing dynamic Dune actions"; maintainers = [ ]; license = lib.licenses.mit; diff --git a/pkgs/development/ocaml-modules/dune-configurator/default.nix b/pkgs/development/ocaml-modules/dune-configurator/default.nix index b94bc4f41ad6..d17a866141b9 100644 --- a/pkgs/development/ocaml-modules/dune-configurator/default.nix +++ b/pkgs/development/ocaml-modules/dune-configurator/default.nix @@ -1,16 +1,14 @@ { lib, buildDunePackage, - dune_3, + dune, csexp, }: buildDunePackage { pname = "dune-configurator"; - inherit (dune_3) src version patches; - - minimalOCamlVersion = "4.05"; + inherit (dune) src version; dontAddPrefix = true; diff --git a/pkgs/development/ocaml-modules/dune-glob/default.nix b/pkgs/development/ocaml-modules/dune-glob/default.nix index bf0f6c92b1bb..0aa452c90a1a 100644 --- a/pkgs/development/ocaml-modules/dune-glob/default.nix +++ b/pkgs/development/ocaml-modules/dune-glob/default.nix @@ -1,16 +1,14 @@ { lib, buildDunePackage, - dune_3, + dune, dune-private-libs, re, }: buildDunePackage { pname = "dune-glob"; - inherit (dune_3) src version; - - duneVersion = "3"; + inherit (dune) src version; dontAddPrefix = true; @@ -20,7 +18,7 @@ buildDunePackage { ]; meta = { - inherit (dune_3.meta) homepage; + inherit (dune.meta) homepage; description = "Glob string matching language supported by dune"; maintainers = [ ]; license = lib.licenses.mit; diff --git a/pkgs/development/ocaml-modules/dune-private-libs/default.nix b/pkgs/development/ocaml-modules/dune-private-libs/default.nix index 57b47f379bcc..7ddaa0b85dc1 100644 --- a/pkgs/development/ocaml-modules/dune-private-libs/default.nix +++ b/pkgs/development/ocaml-modules/dune-private-libs/default.nix @@ -1,18 +1,14 @@ { lib, buildDunePackage, - dune_3, + dune, stdune, }: buildDunePackage { pname = "dune-private-libs"; - duneVersion = "3"; - - inherit (dune_3) src version; - - minimalOCamlVersion = "4.08"; + inherit (dune) src version; dontAddPrefix = true; diff --git a/pkgs/development/ocaml-modules/dune-rpc/default.nix b/pkgs/development/ocaml-modules/dune-rpc/default.nix index 082769eed883..46d20ced5a42 100644 --- a/pkgs/development/ocaml-modules/dune-rpc/default.nix +++ b/pkgs/development/ocaml-modules/dune-rpc/default.nix @@ -1,7 +1,7 @@ { lib, buildDunePackage, - dune_3, + dune, csexp, stdune, ocamlc-loc, @@ -13,9 +13,7 @@ buildDunePackage { pname = "dune-rpc"; - inherit (dune_3) src version; - - duneVersion = "3"; + inherit (dune) src version; dontAddPrefix = true; @@ -31,7 +29,7 @@ buildDunePackage { meta = { description = "Library to connect and control a running dune instance"; - inherit (dune_3.meta) homepage; + inherit (dune.meta) homepage; maintainers = [ ]; license = lib.licenses.mit; }; diff --git a/pkgs/development/ocaml-modules/dune-site/default.nix b/pkgs/development/ocaml-modules/dune-site/default.nix index cb45f571a79e..9ea28352c3ec 100644 --- a/pkgs/development/ocaml-modules/dune-site/default.nix +++ b/pkgs/development/ocaml-modules/dune-site/default.nix @@ -1,15 +1,13 @@ { lib, buildDunePackage, - dune_3, + dune, dune-private-libs, }: buildDunePackage { pname = "dune-site"; - inherit (dune_3) src version; - - duneVersion = "3"; + inherit (dune) src version; dontAddPrefix = true; @@ -17,7 +15,7 @@ buildDunePackage { meta = { description = "Library for embedding location information inside executable and libraries"; - inherit (dune_3.meta) homepage; + inherit (dune.meta) homepage; maintainers = [ ]; license = lib.licenses.mit; }; diff --git a/pkgs/development/ocaml-modules/dyn/default.nix b/pkgs/development/ocaml-modules/dyn/default.nix index 7cbd890cf6b5..08369d579139 100644 --- a/pkgs/development/ocaml-modules/dyn/default.nix +++ b/pkgs/development/ocaml-modules/dyn/default.nix @@ -1,14 +1,13 @@ { buildDunePackage, - dune_3, + dune, ordering, pp, }: buildDunePackage { pname = "dyn"; - inherit (dune_3) version src; - duneVersion = "3"; + inherit (dune) version src; dontAddPrefix = true; @@ -17,7 +16,7 @@ buildDunePackage { pp ]; - meta = dune_3.meta // { + meta = dune.meta // { description = "Dynamic type"; }; } diff --git a/pkgs/development/ocaml-modules/fs-io/default.nix b/pkgs/development/ocaml-modules/fs-io/default.nix index acb431a664ef..be03065352ee 100644 --- a/pkgs/development/ocaml-modules/fs-io/default.nix +++ b/pkgs/development/ocaml-modules/fs-io/default.nix @@ -1,12 +1,12 @@ -{ buildDunePackage, dune_3 }: +{ buildDunePackage, dune }: buildDunePackage { pname = "fs-io"; - inherit (dune_3) version src; + inherit (dune) version src; dontAddPrefix = true; - meta = dune_3.meta // { + meta = dune.meta // { description = "Dune's file system IO library"; }; } diff --git a/pkgs/development/ocaml-modules/top-closure/default.nix b/pkgs/development/ocaml-modules/top-closure/default.nix index 098f89b7b96c..2fa5ad5a54dc 100644 --- a/pkgs/development/ocaml-modules/top-closure/default.nix +++ b/pkgs/development/ocaml-modules/top-closure/default.nix @@ -1,12 +1,12 @@ -{ buildDunePackage, dune_3 }: +{ buildDunePackage, dune }: buildDunePackage { pname = "top-closure"; - inherit (dune_3) version src; + inherit (dune) version src; dontAddPrefix = true; - meta = dune_3.meta // { + meta = dune.meta // { description = "Dune's topological closure library"; }; } From 393875874ee8c5aec628abf94e07fb0702351b84 Mon Sep 17 00:00:00 2001 From: Niklas Halonen Date: Mon, 1 Jun 2026 18:25:24 +0300 Subject: [PATCH 15/55] leangz: init at 0.1.19 --- pkgs/by-name/le/leangz/package.nix | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 pkgs/by-name/le/leangz/package.nix diff --git a/pkgs/by-name/le/leangz/package.nix b/pkgs/by-name/le/leangz/package.nix new file mode 100644 index 000000000000..5af5edd28e53 --- /dev/null +++ b/pkgs/by-name/le/leangz/package.nix @@ -0,0 +1,27 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, +}: +rustPlatform.buildRustPackage (finalAttrs: { + pname = "leangz"; + version = "0.1.19"; # Should match LEANTAR_VERSION in leanprover/lean4/CMakeLists.txt + + src = fetchFromGitHub { + owner = "digama0"; + repo = "leangz"; + rev = "v${finalAttrs.version}"; + hash = "sha256-kDvaydStWiJYCmKjoU39tuOQHNw5Zo577GeAvlENO2o="; + }; + + cargoHash = "sha256-n3iqdRbXcSsCL+8/vDcdOXwnbU9k7DTSKR14gZ4Zlxg="; + + __structuredAttrs = true; + + meta = { + description = "Lean 4 .olean file (de)compressor"; + homepage = "https://github.com/digama0/leangz"; + license = lib.licenses.asl20; + maintainers = with lib.maintainers; [ niklashh ]; + }; +}) From a26b66330f6fa572e7005ee2a1eb031093456e6e Mon Sep 17 00:00:00 2001 From: Niklas Halonen Date: Mon, 1 Jun 2026 18:27:14 +0300 Subject: [PATCH 16/55] lean4: update leanPackages and lean4 4.29.0/1 -> 4.30.0 As reported on FreeBSD forums, updating lean4 to 4.30.0 fails to a leantar related issue. We follow the patch mentioned on the FreeBSD forums and depend on digama0/leangz (that comes with leantar). However, there doesn't seem to be a reason to disable installing leantar, so we don't set INSTALL_LEANTAR=OFF like the patch. References: - https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295656 - https://cgit.freebsd.org/ports/commit/?id=516f8a5764de5c7bdd0e9f7810601a5057bbc650 - https://lean-lang.org/doc/reference/latest/releases/v4.30.0/#release-v4___30___0 - leanprover/lean4#12822 --- pkgs/by-name/le/lean4/package.nix | 8 ++++--- pkgs/development/lean-modules/Cli/default.nix | 11 ++++++---- .../lean-modules/LeanSearchClient/default.nix | 5 ++++- pkgs/development/lean-modules/Qq/default.nix | 11 ++++++---- .../lean-modules/aesop/default.nix | 11 ++++++---- .../lean-modules/batteries/default.nix | 21 +++++++++++++++---- .../lean-modules/importGraph/default.nix | 11 ++++++---- .../lean-modules/lean4/default.nix | 16 +++++++++----- .../lean-modules/mathlib/default.nix | 11 ++++++---- .../lean-modules/plausible/default.nix | 11 ++++++---- .../lean-modules/proofwidgets/default.nix | 5 ++++- 11 files changed, 83 insertions(+), 38 deletions(-) diff --git a/pkgs/by-name/le/lean4/package.nix b/pkgs/by-name/le/lean4/package.nix index dbf5e3b0d7d7..a1016a1b437a 100644 --- a/pkgs/by-name/le/lean4/package.nix +++ b/pkgs/by-name/le/lean4/package.nix @@ -6,6 +6,7 @@ git, gmp, cadical, + leangz, makeWrapper, pkg-config, libuv, @@ -13,13 +14,12 @@ perl, testers, }: - let cadical' = cadical.override { version = "2.1.3"; }; in stdenv.mkDerivation (finalAttrs: { pname = "lean4"; - version = "4.29.1"; + version = "4.30.0"; # Using a vendored version rather than nixpkgs' version to match the exact version required by # Lean. Apparently, even a slight version change can impact greatly the final performance. @@ -34,7 +34,7 @@ stdenv.mkDerivation (finalAttrs: { owner = "leanprover"; repo = "lean4"; tag = "v${finalAttrs.version}"; - hash = "sha256-pdhRPjSic2H8zPJXLmyfN8umKDoafjmSo4OQSRxIbyE="; + hash = "sha256-YTsfIppd6km7wOjAxRH5KMPsW++ztFDCJT2up72J86Q="; }; postPatch = @@ -66,6 +66,7 @@ stdenv.mkDerivation (finalAttrs: { cmake pkg-config makeWrapper + leangz # Provides leantar ]; buildInputs = [ @@ -110,6 +111,7 @@ stdenv.mkDerivation (finalAttrs: { danielbritten jthulhu nadja-y + niklashh ]; mainProgram = "lean"; }; diff --git a/pkgs/development/lean-modules/Cli/default.nix b/pkgs/development/lean-modules/Cli/default.nix index d3bc495ee9cf..07476219fea2 100644 --- a/pkgs/development/lean-modules/Cli/default.nix +++ b/pkgs/development/lean-modules/Cli/default.nix @@ -6,13 +6,13 @@ buildLakePackage { pname = "lean4-cli"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover"; repo = "lean4-cli"; - tag = "v4.29.0"; - hash = "sha256-jCUl4sXVmwtYPuQecEUFH6mwFzPaQY7au4624EOiWjk="; + tag = "v4.30.0"; + hash = "sha256-oMaqHvWlEfk1601JfNKPvkGIWgMW6tiF7Mej7g63vh0="; }; leanPackageName = "Cli"; @@ -31,6 +31,9 @@ buildLakePackage { description = "Command-line argument parser for Lean 4"; homepage = "https://github.com/leanprover/lean4-cli"; license = lib.licenses.mit; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/LeanSearchClient/default.nix b/pkgs/development/lean-modules/LeanSearchClient/default.nix index b7e25b5ecbd0..da6eaf0263d0 100644 --- a/pkgs/development/lean-modules/LeanSearchClient/default.nix +++ b/pkgs/development/lean-modules/LeanSearchClient/default.nix @@ -22,6 +22,9 @@ buildLakePackage { description = "Lean 4 client for LeanSearch and Moogle proof search"; homepage = "https://github.com/leanprover-community/LeanSearchClient"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/Qq/default.nix b/pkgs/development/lean-modules/Qq/default.nix index 6feb6fa51916..a91693775347 100644 --- a/pkgs/development/lean-modules/Qq/default.nix +++ b/pkgs/development/lean-modules/Qq/default.nix @@ -6,13 +6,13 @@ buildLakePackage { pname = "lean4-Qq"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "quote4"; - tag = "v4.29.0"; - hash = "sha256-pNY5hv1nJbreCfU4EewIHCpiryIBv1ghWibrUW8vnQ0="; + tag = "v4.30.0"; + hash = "sha256-jVsRw/R7D7HmsE7vQvVeDXcnVerlcDBOrhf9FJJiXkY="; }; leanPackageName = "Qq"; @@ -21,6 +21,9 @@ buildLakePackage { description = "Lean 4 compile-time quote and antiquote macros for metaprogramming"; homepage = "https://github.com/leanprover-community/quote4"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/aesop/default.nix b/pkgs/development/lean-modules/aesop/default.nix index 7d8ad308741a..f71d9e7c9f47 100644 --- a/pkgs/development/lean-modules/aesop/default.nix +++ b/pkgs/development/lean-modules/aesop/default.nix @@ -7,13 +7,13 @@ buildLakePackage { pname = "lean4-aesop"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "aesop"; - tag = "v4.29.0"; - hash = "sha256-CNwxNig8OWjtfQRYyRnM/HGBn2oaNX5qP9CVT2eWNlg="; + tag = "v4.30.0"; + hash = "sha256-7PhQVMdiYImuzRYdf0Kgw3JYS4nBLfILXxyhFH8Zag0="; }; leanPackageName = "aesop"; @@ -23,6 +23,9 @@ buildLakePackage { description = "White-box automation for Lean 4"; homepage = "https://github.com/leanprover-community/aesop"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/batteries/default.nix b/pkgs/development/lean-modules/batteries/default.nix index 9fdb1efd8686..3e06e945f9fa 100644 --- a/pkgs/development/lean-modules/batteries/default.nix +++ b/pkgs/development/lean-modules/batteries/default.nix @@ -6,21 +6,34 @@ buildLakePackage { pname = "lean4-batteries"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "batteries"; - tag = "v4.29.0"; - hash = "sha256-sEIDi2i2FaLTgKYWt/kzqPrjMdf+bFURfhw6ZZWBawQ="; + tag = "v4.30.0"; + hash = "sha256-OOcKCQEgnn9zkkwjHOovMb/IprNomTDufLOfEXs7hFU="; }; leanPackageName = "batteries"; + # Pre-build static library for downstream executables. + # TODO: upstream this to batteries + postPatch = '' + substituteInPlace lakefile.toml \ + --replace-fail '[[lean_lib]] + name = "Batteries"' '[[lean_lib]] + name = "Batteries" + defaultFacets = ["static"]' + ''; + meta = { description = "The batteries-included extended library for Lean 4"; homepage = "https://github.com/leanprover-community/batteries"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/importGraph/default.nix b/pkgs/development/lean-modules/importGraph/default.nix index 4da481fd5a16..6692a5f99ec2 100644 --- a/pkgs/development/lean-modules/importGraph/default.nix +++ b/pkgs/development/lean-modules/importGraph/default.nix @@ -7,13 +7,13 @@ buildLakePackage { pname = "lean4-importGraph"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "import-graph"; - tag = "v4.29.0"; - hash = "sha256-tqdO2qyWiJzEbK0yuu4+tiOXTEg9XJfGnI7z6Jh/abg="; + tag = "v4.30.0"; + hash = "sha256-V3bGQxTNs2G4MqaVxRb6WED1a7VaHfEo1HgBNqPipz8="; }; leanPackageName = "importGraph"; @@ -23,6 +23,9 @@ buildLakePackage { description = "Tools to analyse and visualise Lean 4 import structures"; homepage = "https://github.com/leanprover-community/import-graph"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/lean4/default.nix b/pkgs/development/lean-modules/lean4/default.nix index 7ba6dd5170f4..2e1c9b2f3c02 100644 --- a/pkgs/development/lean-modules/lean4/default.nix +++ b/pkgs/development/lean-modules/lean4/default.nix @@ -8,16 +8,18 @@ git, gmp, cadical, + leangz, pkg-config, libuv, perl, testers, }: - let + cadical' = cadical.override { version = "2.1.3"; }; + lean4 = stdenv.mkDerivation (finalAttrs: { pname = "lean4"; - version = "4.29.0"; + version = "4.30.0"; mimalloc-src = fetchFromGitHub { owner = "microsoft"; @@ -30,7 +32,7 @@ let owner = "leanprover"; repo = "lean4"; tag = "v${finalAttrs.version}"; - hash = "sha256-0v4OTrCLdHBbWJUq7hIjJonqget9SvsG3izGlOwhwyU="; + hash = "sha256-YTsfIppd6km7wOjAxRH5KMPsW++ztFDCJT2up72J86Q="; }; # Vendor mimalloc. Upstream has since partially adopted FetchContent: @@ -70,12 +72,13 @@ let nativeBuildInputs = [ cmake pkg-config + leangz # Provides leantar ]; buildInputs = [ gmp libuv - cadical + cadical' ]; nativeCheckInputs = [ @@ -103,7 +106,10 @@ let changelog = "https://github.com/leanprover/lean4/blob/${finalAttrs.src.tag}/RELEASES.md"; license = lib.licenses.asl20; platforms = lib.platforms.all; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; mainProgram = "lean"; }; }); diff --git a/pkgs/development/lean-modules/mathlib/default.nix b/pkgs/development/lean-modules/mathlib/default.nix index 70a34dd9f333..60ae77331f02 100644 --- a/pkgs/development/lean-modules/mathlib/default.nix +++ b/pkgs/development/lean-modules/mathlib/default.nix @@ -14,13 +14,13 @@ buildLakePackage { pname = "lean4-mathlib"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "mathlib4"; - tag = "v4.29.0"; - hash = "sha256-fe+qS7gNxdLnACX3/jqToa9m7r1gbskY6kDJbm1ZefE="; + tag = "v4.30.0"; + hash = "sha256-RxOxdUiVUAxUbfVhxlkjmPX1V64EtmIIn1eW75TiJWA="; }; leanPackageName = "mathlib"; @@ -44,6 +44,9 @@ buildLakePackage { description = "Mathematical library for Lean 4"; homepage = "https://github.com/leanprover-community/mathlib4"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/plausible/default.nix b/pkgs/development/lean-modules/plausible/default.nix index 07b22b3ebe87..6f4d08d0ee28 100644 --- a/pkgs/development/lean-modules/plausible/default.nix +++ b/pkgs/development/lean-modules/plausible/default.nix @@ -6,13 +6,13 @@ buildLakePackage { pname = "lean4-plausible"; - version = "4.29.0"; + version = "4.30.0"; src = fetchFromGitHub { owner = "leanprover-community"; repo = "plausible"; - tag = "v4.29.0"; - hash = "sha256-08fNB2GK5AqDJ15n5Ol+HYqaSbsznyp4cerDo32bG50="; + tag = "v4.30.0"; + hash = "sha256-DSaS0W2cfCUh2N+7WyiM7aUv3trtRNON0PzCgCW2SKY="; }; leanPackageName = "plausible"; @@ -21,6 +21,9 @@ buildLakePackage { description = "Property-based testing framework for Lean 4"; homepage = "https://github.com/leanprover-community/plausible"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } diff --git a/pkgs/development/lean-modules/proofwidgets/default.nix b/pkgs/development/lean-modules/proofwidgets/default.nix index 54998125b500..3a2793272012 100644 --- a/pkgs/development/lean-modules/proofwidgets/default.nix +++ b/pkgs/development/lean-modules/proofwidgets/default.nix @@ -62,6 +62,9 @@ buildLakePackage { description = "Interactive UI framework for Lean 4 proof assistants"; homepage = "https://github.com/leanprover-community/ProofWidgets4"; license = lib.licenses.asl20; - maintainers = with lib.maintainers; [ nadja-y ]; + maintainers = with lib.maintainers; [ + nadja-y + niklashh + ]; }; } From 801d8901cdeacec4e051fb256bdd350c96debf74 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Mon, 1 Jun 2026 19:40:03 +0000 Subject: [PATCH 17/55] python3Packages.modelscope: 1.37.0 -> 1.37.1 --- pkgs/development/python-modules/modelscope/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/development/python-modules/modelscope/default.nix b/pkgs/development/python-modules/modelscope/default.nix index 0f2c57694056..0285a964d620 100644 --- a/pkgs/development/python-modules/modelscope/default.nix +++ b/pkgs/development/python-modules/modelscope/default.nix @@ -11,14 +11,14 @@ buildPythonPackage (finalAttrs: { pname = "modelscope"; - version = "1.37.0"; + version = "1.37.1"; pyproject = true; src = fetchFromGitHub { owner = "modelscope"; repo = "modelscope"; tag = "v${finalAttrs.version}"; - hash = "sha256-kGcu1iojClUhj8KS+TY0WU8+dKRanqbkJmXwSE0EoLk="; + hash = "sha256-LNg2JtqqID6RKuFi+j29NfOWuNZhkkTIdKmL9bXzAvs="; }; build-system = [ setuptools ]; From 296407053cc40cb777776a1fd209f12ea974372b Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Mon, 1 Jun 2026 20:28:38 +0000 Subject: [PATCH 18/55] lstk: 0.9.0 -> 0.10.0 --- pkgs/by-name/ls/lstk/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ls/lstk/package.nix b/pkgs/by-name/ls/lstk/package.nix index 852358acc641..b9e89be7d2f3 100644 --- a/pkgs/by-name/ls/lstk/package.nix +++ b/pkgs/by-name/ls/lstk/package.nix @@ -7,7 +7,7 @@ }: buildGoModule (finalAttrs: { pname = "lstk"; - version = "0.9.0"; + version = "0.10.0"; __structuredAttrs = true; @@ -15,10 +15,10 @@ buildGoModule (finalAttrs: { owner = "localstack"; repo = "lstk"; tag = "v${finalAttrs.version}"; - sha256 = "sha256-wfU7b70SZfv/4hvTRAqfE807dyUW32j2M1V/k3R2Z20="; + sha256 = "sha256-hypUvZWhYIT1WTd8M/DVCY1Cy4EApSwSnPWLIfc9Nng="; }; - vendorHash = "sha256-y1qzHSKJS2k98UicoUPmctsGQGiXweNbWKMsFpvYBMo="; + vendorHash = "sha256-y/QlgdYS4IeU9Xf/2trHRvjB5QOHDbFDrF57y9B6jxI="; excludedPackages = "test/integration"; From ad64815fc28223e74e5fa709e8774d63c4c688a7 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Mon, 1 Jun 2026 20:49:23 +0000 Subject: [PATCH 19/55] bmm: 0.3.0 -> 0.3.1 --- pkgs/by-name/bm/bmm/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/bm/bmm/package.nix b/pkgs/by-name/bm/bmm/package.nix index a73dff2e6f5b..24d8cd7e1ac6 100644 --- a/pkgs/by-name/bm/bmm/package.nix +++ b/pkgs/by-name/bm/bmm/package.nix @@ -6,16 +6,16 @@ }: rustPlatform.buildRustPackage (finalAttrs: { pname = "bmm"; - version = "0.3.0"; + version = "0.3.1"; src = fetchFromGitHub { owner = "dhth"; repo = "bmm"; tag = "v${finalAttrs.version}"; - hash = "sha256-sfAUvvZ/LKOXfnA0PB3LRbPHYoA+FJV4frYU+BpC6WI="; + hash = "sha256-SASf13BFNz4ZlKJJk6O/Euv+wA26ov0QKsaEKRc24d0="; }; - cargoHash = "sha256-+o8bYi4Pe9zwiDBUMllpF+my7gp3iLX0+DntFtN7PoI="; + cargoHash = "sha256-AOGNMFAr32WZnyw5nNQa6svLpSc3UQonZ7RjZ5zap1I="; doInstallCheck = true; From d0ad9e48322f22b18da75d34b79dbd23ba5abfe1 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 02:48:41 +0000 Subject: [PATCH 20/55] nerva: 1.4.0 -> 1.4.2 --- pkgs/by-name/ne/nerva/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/ne/nerva/package.nix b/pkgs/by-name/ne/nerva/package.nix index ff5043275208..aba6ee8996d9 100644 --- a/pkgs/by-name/ne/nerva/package.nix +++ b/pkgs/by-name/ne/nerva/package.nix @@ -6,13 +6,13 @@ buildGoModule (finalAttrs: { pname = "nerva"; - version = "1.4.0"; + version = "1.4.2"; src = fetchFromGitHub { owner = "praetorian-inc"; repo = "nerva"; tag = "v${finalAttrs.version}"; - hash = "sha256-9Vb7hUyJJZ/zKv+nhoX0HX2njCqU49LhM8ihm7Or/xE="; + hash = "sha256-xARdj2oo097jwfXy/FwMg1KMIXFJdjCV90W0lEPw8KM="; }; vendorHash = "sha256-j+8KZxHnYrtxwdBxpAXZ+Q5Sm1REluUEmD69tKYTCag="; From 2f7a6d7ebd90be5c445664fd103e1377f3df7551 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 06:12:12 +0000 Subject: [PATCH 21/55] biome: 2.4.15 -> 2.4.16 --- pkgs/by-name/bi/biome/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/bi/biome/package.nix b/pkgs/by-name/bi/biome/package.nix index c4fba227b0de..b04b388ca36e 100644 --- a/pkgs/by-name/bi/biome/package.nix +++ b/pkgs/by-name/bi/biome/package.nix @@ -11,16 +11,16 @@ }: rustPlatform.buildRustPackage (finalAttrs: { pname = "biome"; - version = "2.4.15"; + version = "2.4.16"; src = fetchFromGitHub { owner = "biomejs"; repo = "biome"; rev = "@biomejs/biome@${finalAttrs.version}"; - hash = "sha256-Q7yx5ZKIrZdnsG3OS9CZ3jyuv71V7l9crCwYRZDuFpU="; + hash = "sha256-YfPaSSNESFdGjJjB3C3rubydZW/U7NQ/HtTlXtH7VB4="; }; - cargoHash = "sha256-UzTE+Grg6RaTWAYIsaKgluVsSZXbDwIK5HY9rY2oIVo="; + cargoHash = "sha256-N5rKXNrLs/J4uDHYVDMl+jSRowB8ipjvJdIHNvJvAUU="; nativeBuildInputs = [ pkg-config ]; From ae6dd33f4c8164d8171fe5acaaf5744c2428c71b Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 06:51:22 +0000 Subject: [PATCH 22/55] python3Packages.elevenlabs: 2.49.1 -> 2.50.0 --- pkgs/development/python-modules/elevenlabs/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/development/python-modules/elevenlabs/default.nix b/pkgs/development/python-modules/elevenlabs/default.nix index 43747629b151..3a2fee59fc6c 100644 --- a/pkgs/development/python-modules/elevenlabs/default.nix +++ b/pkgs/development/python-modules/elevenlabs/default.nix @@ -14,14 +14,14 @@ buildPythonPackage (finalAttrs: { pname = "elevenlabs"; - version = "2.49.1"; + version = "2.50.0"; pyproject = true; src = fetchFromGitHub { owner = "elevenlabs"; repo = "elevenlabs-python"; tag = "v${finalAttrs.version}"; - hash = "sha256-ATZhkrM+IPCt9Y+7/C5Ng8gKcCFZPHTz1CN3OJ/9uZI="; + hash = "sha256-OIkELIVPXjSqp9L7GqWwodLUrXpHa6GYKSeHF0fbhIk="; }; build-system = [ poetry-core ]; From 3026af3f4932ead12bdfabb1d31f90fec19e8b21 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 06:55:35 +0000 Subject: [PATCH 23/55] monkeys-audio: 12.97 -> 13.01 --- pkgs/by-name/mo/monkeys-audio/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/mo/monkeys-audio/package.nix b/pkgs/by-name/mo/monkeys-audio/package.nix index 11ceda73c3f0..8872c762b6fa 100644 --- a/pkgs/by-name/mo/monkeys-audio/package.nix +++ b/pkgs/by-name/mo/monkeys-audio/package.nix @@ -6,12 +6,12 @@ }: stdenv.mkDerivation (finalAttrs: { - version = "12.97"; + version = "13.01"; pname = "monkeys-audio"; src = fetchzip { url = "https://monkeysaudio.com/files/MAC_${builtins.concatStringsSep "" (lib.strings.splitString "." finalAttrs.version)}_SDK.zip"; - hash = "sha256-Ra6F+msApdIIJdYLcv+EysnDPmAXDYI8W3theLla25w="; + hash = "sha256-8rJVQ4Z2yyepgnkxdeMx1iw8o24i9DrTm+UIGdXwlGI="; stripRoot = false; }; From cbf54124841ca3d008fa9061cb5e471305b0bf9c Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 06:56:32 +0000 Subject: [PATCH 24/55] typos: 1.46.3 -> 1.47.0 --- pkgs/by-name/ty/typos/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ty/typos/package.nix b/pkgs/by-name/ty/typos/package.nix index 31b34c5685d4..816ff0f32537 100644 --- a/pkgs/by-name/ty/typos/package.nix +++ b/pkgs/by-name/ty/typos/package.nix @@ -8,16 +8,16 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "typos"; - version = "1.46.3"; + version = "1.47.0"; src = fetchFromGitHub { owner = "crate-ci"; repo = "typos"; tag = "v${finalAttrs.version}"; - hash = "sha256-vxBFu+zflG56spXPRrvnXtgDUIrWndjBkcuZheMHwEo="; + hash = "sha256-ESKwH9w6TXmn4sqiaZua7cDi6BbwgKtP6dPgedGNhC8="; }; - cargoHash = "sha256-8fOZgCLjQrl/UyCMXY2NOrC96kuICNPOtpdYiVZ1rrY="; + cargoHash = "sha256-0iGunX9HbZXmpwJA5bOr60cBiATcDmnI0+OpNipo0oY="; passthru.updateScript = nix-update-script { }; From 75a2495a8ef62c37a9d63ffa3605700782e1f07f Mon Sep 17 00:00:00 2001 From: Sizhe Zhao Date: Mon, 1 Jun 2026 14:27:33 +0800 Subject: [PATCH 25/55] python3Packages.cuda-tile: init at 1.4.0 Co-authored-by: Gaetan Lepage --- .../python-modules/cuda-tile/default.nix | 127 ++++++++++++++++++ pkgs/top-level/python-packages.nix | 2 + 2 files changed, 129 insertions(+) create mode 100644 pkgs/development/python-modules/cuda-tile/default.nix diff --git a/pkgs/development/python-modules/cuda-tile/default.nix b/pkgs/development/python-modules/cuda-tile/default.nix new file mode 100644 index 000000000000..05215d6e3f41 --- /dev/null +++ b/pkgs/development/python-modules/cuda-tile/default.nix @@ -0,0 +1,127 @@ +{ + lib, + config, + buildPythonPackage, + fetchFromGitHub, + + # build-system + setuptools, + + # dependencies + typing-extensions, + + # nativeBuildInputs + cmake, + cudaPackages, + + # buildInputs + dlpack, + + # tests + pytestCheckHook, + torch, + + # passthru + cuda-tile, +}: +let + # https://github.com/NVIDIA/cutile-python/blob/v1.4.0/cmake/FetchXLAHeaders.cmake#L5-L6 + xla = fetchFromGitHub { + owner = "openxla"; + repo = "xla"; + rev = "b6f37ab7767f428fd6f993de5e211643d47d4deb"; + hash = "sha256-U4e3k4nm9gB1x5hahXwycWSryBQuxIPmOzVf6kuahY0="; + }; +in +buildPythonPackage.override { stdenv = cudaPackages.backendStdenv; } (finalAttrs: { + pname = "cuda-tile"; + version = "1.4.0"; + pyproject = true; + __structuredAttrs = true; + + src = fetchFromGitHub { + owner = "NVIDIA"; + repo = "cutile-python"; + tag = "v${finalAttrs.version}"; + hash = "sha256-R5V69nJLQ3/1995ezH1/WuueA6cm1vhKZdOECqbwPbU="; + }; + + postPatch = '' + substituteInPlace pyproject.toml \ + --replace-fail "setuptools==80.10.2" "setuptools" + '' + # Otherwise fails to find libc + # xla_ffi.cpp:(.text+0x308): undefined reference to `__stack_chk_fail' + + '' + substituteInPlace cext/CMakeLists.txt \ + --replace-fail \ + "target_link_libraries(_cext_shared _cext_static \''${Python_LIBRARIES} \''${asan_library})" \ + "target_link_libraries(_cext_shared _cext_static \''${Python_LIBRARIES} \''${asan_library} c)" + '' + # Get rid of the vendored broken logic for finding the CUDA toolkit + + '' + rm cmake/FindCUDAToolkit.cmake + '' + # Manually inject the library version + + '' + echo "${finalAttrs.version}" >src/cuda/tile/VERSION + ''; + + build-system = [ + setuptools + ]; + + env = { + CUDA_TILE_CMAKE_DLPACK_PATH = dlpack; + CUDA_TILE_CMAKE_XLA_PATH = xla; + }; + + nativeBuildInputs = [ + cmake + cudaPackages.cuda_nvcc + ]; + dontUseCmakeConfigure = true; + + buildInputs = [ + cudaPackages.cuda_cudart # cuda.h + ]; + + dependencies = [ + typing-extensions + ]; + + optional-dependencies = { + tileiras = [ + # unpackaged as it doesn't make sense to have it in nixpkgs + # cuda-toolkit + ]; + }; + + pythonImportsCheck = [ "cuda.tile" ]; + + nativeCheckInputs = [ + pytestCheckHook + torch + ]; + + # Tests require access to a physical GPU + doCheck = false; + + passthru.gpuCheck = cuda-tile.overridePythonAttrs { + requiredSystemFeatures = [ "cuda" ]; + doCheck = true; + }; + + meta = { + description = "Programming model for writing parallel kernels for NVIDIA GPUs"; + homepage = "https://docs.nvidia.com/cuda/cutile-python/"; + downloadPage = "https://github.com/NVIDIA/cutile-python"; + changelog = "https://docs.nvidia.com/cuda/cutile-python/generated/release_notes.html"; + license = lib.licenses.asl20; + maintainers = with lib.maintainers; [ + GaetanLepage + prince213 + ]; + broken = !config.cudaSupport; + }; +}) diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index a586665fc53c..f4ff36be4137 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -3438,6 +3438,8 @@ self: super: with self; { cuda-pathfinder = callPackage ../development/python-modules/cuda-pathfinder { }; + cuda-tile = callPackage ../development/python-modules/cuda-tile { }; + cupy = callPackage ../development/python-modules/cupy { }; curated-tokenizers = callPackage ../development/python-modules/curated-tokenizers { }; From c16f99a4d55662447b8e6ca911e087570538769a Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 07:31:09 +0000 Subject: [PATCH 26/55] par-lang: 0-unstable-2026-05-23 -> 0-unstable-2026-06-01 --- pkgs/by-name/pa/par-lang/package.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/by-name/pa/par-lang/package.nix b/pkgs/by-name/pa/par-lang/package.nix index 9c039e3e57d1..4d89a5c7713c 100644 --- a/pkgs/by-name/pa/par-lang/package.nix +++ b/pkgs/by-name/pa/par-lang/package.nix @@ -19,16 +19,16 @@ rustPlatform.buildRustPackage { pname = "par-lang"; - version = "0-unstable-2026-05-23"; + version = "0-unstable-2026-06-01"; src = fetchFromGitHub { owner = "par-team"; repo = "par-lang"; - rev = "56ecfe8ee657e5ef853bf29a791f3779cca8bae3"; - hash = "sha256-St1mIPuX0BMibOWHhEQ73/V0ZY6tHuxb7/VVI3hOxfk="; + rev = "b4cb6f58a696d0c159df241c302f6dac220a0421"; + hash = "sha256-FO3XhIjead6Om/1wMWTFq6cx3pGps8JKs1/7MN1fllo="; }; - cargoHash = "sha256-lX67nywrM0SZp6qbvTeojVVp5ZSvAEw5fvklQ+SX1hU="; + cargoHash = "sha256-IoZbNvCzeuOMVjfbTUGr+qs73IvFmPTK9rn6x40SYBQ="; nativeBuildInputs = [ pkg-config From 9d1a8dba42504f817bcbff369e7cb1edc7242a05 Mon Sep 17 00:00:00 2001 From: K900 Date: Tue, 2 Jun 2026 11:20:17 +0300 Subject: [PATCH 27/55] Revert "nixos/virtualisation: remove hard-coded virtio-gpu-pci device from aarch machines" This reverts commit 44c6c2ef166cc796b417d9885b966489b23a2bf9. The change breaks nixosTests.login, blocking channels. --- nixos/modules/virtualisation/qemu-vm.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 4fe5ed1e56c0..c6da8d9aaa6f 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -1379,6 +1379,7 @@ in "-device usb-tablet,bus=usb-bus.0" ]) (mkIf pkgs.stdenv.hostPlatform.isAarch [ + "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" From 2484de99b7c28af38fa658075e1eccc336e32f72 Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 09:11:53 +0000 Subject: [PATCH 28/55] terraform-providers.huaweicloud_huaweicloud: 1.91.0 -> 1.92.0 --- .../networking/cluster/terraform-providers/providers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/applications/networking/cluster/terraform-providers/providers.json b/pkgs/applications/networking/cluster/terraform-providers/providers.json index d77a12b33f9c..782d01cedc53 100644 --- a/pkgs/applications/networking/cluster/terraform-providers/providers.json +++ b/pkgs/applications/networking/cluster/terraform-providers/providers.json @@ -724,11 +724,11 @@ "vendorHash": "sha256-f49amYWzWSG9tzY6wvpxtTFiyJ8zC/Lc1hIQtzdgJRs=" }, "huaweicloud_huaweicloud": { - "hash": "sha256-CtqPtXccE6I+yDj/7XbjbACMwCGMv+pSEIa5DVh+AGo=", + "hash": "sha256-4KRJhLnpKZwmL5aQqva8JZGDCYBzlGepE7zP6hwP+KY=", "homepage": "https://registry.terraform.io/providers/huaweicloud/huaweicloud", "owner": "huaweicloud", "repo": "terraform-provider-huaweicloud", - "rev": "v1.91.0", + "rev": "v1.92.0", "spdx": "MPL-2.0", "vendorHash": null }, From ac2410be5d061a429601d12b0676c7adcbb8cbb5 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 24 Sep 2025 17:21:02 +0200 Subject: [PATCH 29/55] nixos/systemd-boot-builder: format --- .../systemd-boot/systemd-boot-builder.py | 197 +++++++++++++----- 1 file changed, 143 insertions(+), 54 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 90241b92cd5f..2804aecbddb5 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -19,9 +19,11 @@ from pathlib import Path EFI_SYS_MOUNT_POINT = Path("@efiSysMountPoint@") BOOT_MOUNT_POINT = Path("@bootMountPoint@") LOADER_CONF = EFI_SYS_MOUNT_POINT / "loader/loader.conf" # Always stored on the ESP -NIXOS_DIR = Path("@nixosDir@".strip("/")) # Path relative to the XBOOTLDR or ESP mount point +NIXOS_DIR = Path( + "@nixosDir@".strip("/") +) # Path relative to the XBOOTLDR or ESP mount point TIMEOUT = "@timeout@" -EDITOR = "@editor@" == "1" # noqa: PLR0133 +EDITOR = "@editor@" == "1" # noqa: PLR0133 CONSOLE_MODE = "@consoleMode@" BOOTSPEC_TOOLS = "@bootspecTools@" DISTRO_NAME = "@distroName@" @@ -35,6 +37,7 @@ COPY_EXTRA_FILES = "@copyExtraFiles@" CHECK_MOUNTPOINTS = "@checkMountpoints@" STORE_DIR = "@storeDir@" + @dataclass class BootSpec: init: Path @@ -54,9 +57,13 @@ libc = ctypes.CDLL("libc.so.6") FILE = None | int -def run(cmd: Sequence[str | Path], stdout: FILE = None) -> subprocess.CompletedProcess[str]: + +def run( + cmd: Sequence[str | Path], stdout: FILE = None +) -> subprocess.CompletedProcess[str]: return subprocess.run(cmd, check=True, text=True, stdout=stdout) + class SystemIdentifier(NamedTuple): profile: str | None generation: int @@ -73,17 +80,23 @@ def copy_if_not_exists(source: Path, dest: Path) -> None: def generation_dir(profile: str | None, generation: int) -> Path: if profile: - return Path(f"/nix/var/nix/profiles/system-profiles/{profile}-{generation}-link") + return Path( + f"/nix/var/nix/profiles/system-profiles/{profile}-{generation}-link" + ) else: return Path(f"/nix/var/nix/profiles/system-{generation}-link") -def system_dir(profile: str | None, generation: int, specialisation: str | None) -> Path: + +def system_dir( + profile: str | None, generation: int, specialisation: str | None +) -> Path: d = generation_dir(profile, generation) if specialisation: return d / "specialisation" / specialisation else: return d + BOOT_ENTRY = """title {title} sort-key {sort_key} version Generation {generation} {description} @@ -92,7 +105,10 @@ initrd {initrd} options {kernel_params} """ -def generation_conf_filename(profile: str | None, generation: int, specialisation: str | None) -> str: + +def generation_conf_filename( + profile: str | None, generation: int, specialisation: str | None +) -> str: pieces = [ "nixos", profile or None, @@ -103,11 +119,16 @@ def generation_conf_filename(profile: str | None, generation: int, specialisatio return "-".join(p for p in pieces if p) + ".conf" -def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None: +def write_loader_conf( + profile: str | None, generation: int, specialisation: str | None +) -> None: tmp = LOADER_CONF.with_suffix(".tmp") - with tmp.open('x') as f: + with tmp.open("x") as f: f.write(f"timeout {TIMEOUT}\n") - f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation)) + f.write( + "default %s\n" + % generation_conf_filename(profile, generation, specialisation) + ) if not EDITOR: f.write("editor 0\n") if REBOOT_FOR_BITLOCKER: @@ -127,7 +148,9 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec: try: bootspec_json = json.load(f) except ValueError as e: - print(f"error: Malformed Json: {e}, in {boot_json_path}", file=sys.stderr) + print( + f"error: Malformed Json: {e}, in {boot_json_path}", file=sys.stderr + ) sys.exit(1) else: boot_json_str = run( @@ -143,17 +166,18 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec: bootspec_json = json.loads(boot_json_str) return bootspec_from_json(bootspec_json) + def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec: - specialisations = bootspec_json['org.nixos.specialisation.v1'] + specialisations = bootspec_json["org.nixos.specialisation.v1"] specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} - systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {}) - sortKey = systemdBootExtension.get('sortKey', 'nixos') - devicetree = systemdBootExtension.get('devicetree') + systemdBootExtension = bootspec_json.get("org.nixos.systemd-boot", {}) + sortKey = systemdBootExtension.get("sortKey", "nixos") + devicetree = systemdBootExtension.get("devicetree") if devicetree: devicetree = Path(devicetree) - main_json = bootspec_json['org.nixos.bootspec.v1'] + main_json = bootspec_json["org.nixos.bootspec.v1"] for attr in ("kernel", "initrd", "toplevel"): if attr in main_json: main_json[attr] = Path(main_json[attr]) @@ -172,24 +196,35 @@ def copy_from_file(file: Path, dry_run: bool = False) -> Path: store_file_path = file.resolve() suffix = store_file_path.name store_subdir = store_file_path.relative_to(STORE_DIR).parts[0] - efi_file_path = NIXOS_DIR / (f"{suffix}.efi" if suffix == store_subdir else f"{store_subdir}-{suffix}.efi") + efi_file_path = NIXOS_DIR / ( + f"{suffix}.efi" if suffix == store_subdir else f"{store_subdir}-{suffix}.efi" + ) if not dry_run: copy_if_not_exists(store_file_path, BOOT_MOUNT_POINT / efi_file_path) return efi_file_path -def write_entry(profile: str | None, generation: int, specialisation: str | None, - machine_id: str | None, bootspec: BootSpec, current: bool) -> None: +def write_entry( + profile: str | None, + generation: int, + specialisation: str | None, + machine_id: str | None, + bootspec: BootSpec, + current: bool, +) -> None: if specialisation: bootspec = bootspec.specialisations[specialisation] kernel = copy_from_file(bootspec.kernel) initrd = copy_from_file(bootspec.initrd) - devicetree = copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None + devicetree = ( + copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None + ) title = "{name}{profile}{specialisation}".format( name=DISTRO_NAME, profile=" [" + profile + "]" if profile else "", - specialisation=" (%s)" % specialisation if specialisation else "") + specialisation=" (%s)" % specialisation if specialisation else "", + ) try: if bootspec.initrdSecrets is not None: @@ -199,26 +234,40 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None print("failed to create initrd secrets!", file=sys.stderr) sys.exit(1) else: - print("warning: failed to create initrd secrets " - f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr) - print("note: this is normal after having removed " - "or renamed a file in `boot.initrd.secrets`", file=sys.stderr) - entry_file = BOOT_MOUNT_POINT / "loader/entries" / generation_conf_filename(profile, generation, specialisation) + print( + "warning: failed to create initrd secrets " + f'for "{title} - Configuration {generation}", an older generation', + file=sys.stderr, + ) + print( + "note: this is normal after having removed " + "or renamed a file in `boot.initrd.secrets`", + file=sys.stderr, + ) + entry_file = ( + BOOT_MOUNT_POINT + / "loader/entries" + / generation_conf_filename(profile, generation, specialisation) + ) tmp_path = entry_file.with_suffix(".tmp") kernel_params = "init=%s " % bootspec.init kernel_params = kernel_params + " ".join(bootspec.kernelParams) build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) - build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F') + build_date = datetime.datetime.fromtimestamp(build_time).strftime("%F") with tmp_path.open("w") as f: - f.write(BOOT_ENTRY.format(title=title, - sort_key=bootspec.sortKey, - generation=generation, - kernel=f"/{kernel}", - initrd=f"/{initrd}", - kernel_params=kernel_params, - description=f"{bootspec.label}, built on {build_date}")) + f.write( + BOOT_ENTRY.format( + title=title, + sort_key=bootspec.sortKey, + generation=generation, + kernel=f"/{kernel}", + initrd=f"/{initrd}", + kernel_params=kernel_params, + description=f"{bootspec.label}, built on {build_date}", + ) + ) if machine_id is not None: f.write("machine-id %s\n" % machine_id) if devicetree is not None: @@ -245,9 +294,7 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]: configurationLimit = CONFIGURATION_LIMIT configurations = [ SystemIdentifier( - profile=profile, - generation=int(line.split()[0]), - specialisation=None + profile=profile, generation=int(line.split()[0]), specialisation=None ) for line in gen_lines ] @@ -256,7 +303,9 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]: def remove_old_entries(gens: list[SystemIdentifier]) -> None: rex_profile = re.compile(r"^nixos-(.*)-generation-.*\.conf$") - rex_generation = re.compile(r"^nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$") + rex_generation = re.compile( + r"^nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$" + ) known_paths = [] for gen in gens: bootspec = get_bootspec(gen.profile, gen.generation) @@ -264,7 +313,9 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None: known_paths.append(copy_from_file(bootspec.initrd, True).name) if bootspec.devicetree is not None: known_paths.append(copy_from_file(bootspec.devicetree, True).name) - for path in (BOOT_MOUNT_POINT / "loader/entries").glob("nixos*-generation-[1-9]*.conf", case_sensitive=False): + for path in (BOOT_MOUNT_POINT / "loader/entries").glob( + "nixos*-generation-[1-9]*.conf", case_sensitive=False + ): if rex_profile.match(path.name): prof = rex_profile.sub(r"\1", path.name) else: @@ -291,12 +342,13 @@ def cleanup_esp() -> None: def get_profiles() -> list[str]: system_profiles = Path("/nix/var/nix/profiles/system-profiles/") if system_profiles.is_dir(): - return [x.name - for x in system_profiles.iterdir() - if not x.name.endswith("-link")] + return [ + x.name for x in system_profiles.iterdir() if not x.name.endswith("-link") + ] else: return [] + def install_bootloader(args: argparse.Namespace) -> None: try: with open("/etc/machine-id") as machine_file: @@ -307,7 +359,10 @@ def install_bootloader(args: argparse.Namespace) -> None: machine_id = None if os.getenv("NIXOS_INSTALL_GRUB") == "1": - warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning) + warnings.warn( + "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", + DeprecationWarning, + ) os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1" # flags to pass to bootctl install/update @@ -351,13 +406,18 @@ def install_bootloader(args: argparse.Namespace) -> None: # ESP: /boot (/dev/disk/by-partuuid/9b39b4c4-c48b-4ebf-bfea-a56b2395b7e0) # File: ├─/EFI/systemd/HashTool.efi # └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 255.2) - installed_match = re.search(r"^\W+.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$", - installed_out, re.IGNORECASE | re.MULTILINE) + installed_match = re.search( + r"^\W+.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$", + installed_out, + re.IGNORECASE | re.MULTILINE, + ) available_match = re.search(r"^\((.*)\)$", available_out) if installed_match is None: - raise Exception("Could not find any previously installed systemd-boot. If you are switching to systemd-boot from a different bootloader, you need to run `nixos-rebuild switch --install-bootloader`") + raise Exception( + "Could not find any previously installed systemd-boot. If you are switching to systemd-boot from a different bootloader, you need to run `nixos-rebuild switch --install-bootloader`" + ) if available_match is None: raise Exception("could not determine systemd-boot version") @@ -366,7 +426,11 @@ def install_bootloader(args: argparse.Namespace) -> None: available_version = available_match.group(1) if installed_version < available_version: - print("updating systemd-boot from %s to %s" % (installed_version, available_version), file=sys.stderr) + print( + "updating systemd-boot from %s to %s" + % (installed_version, available_version), + file=sys.stderr, + ) run( [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags @@ -388,14 +452,28 @@ def install_bootloader(args: argparse.Namespace) -> None: is_default = Path(bootspec.init).parent == Path(args.default_config) write_entry(*gen, machine_id, bootspec, current=is_default) for specialisation in bootspec.specialisations.keys(): - write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default) + write_entry( + gen.profile, + gen.generation, + specialisation, + machine_id, + bootspec, + current=is_default, + ) if is_default: write_loader_conf(*gen) except OSError as e: # See https://github.com/NixOS/nixpkgs/issues/114552 if e.errno == errno.EINVAL: - profile = f"profile '{gen.profile}'" if gen.profile else "default profile" - print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr) + profile = ( + f"profile '{gen.profile}'" if gen.profile else "default profile" + ) + print( + "ignoring {} in the list of boot entries because of the following error:\n{}".format( + profile, e + ), + file=sys.stderr, + ) else: raise e @@ -425,8 +503,14 @@ def install_bootloader(args: argparse.Namespace) -> None: def main() -> None: - parser = argparse.ArgumentParser(description=f"Update {DISTRO_NAME}-related systemd-boot files") - parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot") + parser = argparse.ArgumentParser( + description=f"Update {DISTRO_NAME}-related systemd-boot files" + ) + parser.add_argument( + "default_config", + metavar="DEFAULT-CONFIG", + help=f"The default {DISTRO_NAME} config to boot", + ) args = parser.parse_args() run([CHECK_MOUNTPOINTS]) @@ -440,13 +524,18 @@ def main() -> None: # event sync the efi filesystem after each update. rc = libc.syncfs(os.open(f"{BOOT_MOUNT_POINT}", os.O_RDONLY)) if rc != 0: - print(f"could not sync {BOOT_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) + print( + f"could not sync {BOOT_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr + ) if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: rc = libc.syncfs(os.open(EFI_SYS_MOUNT_POINT, os.O_RDONLY)) if rc != 0: - print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) + print( + f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", + file=sys.stderr, + ) -if __name__ == '__main__': +if __name__ == "__main__": main() From 69ce6b2391607b960184eede5db97f155090a554 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 24 Sep 2025 17:52:52 +0200 Subject: [PATCH 30/55] nixos/systemd-boot-builder: re-instate boot counting Co-Authored-By: Julien Malka Co-Authored-By: AkechiShiro <14914796+AkechiShiro@users.noreply.github.com> --- .../systemd-boot/systemd-boot-builder.py | 309 +++++++++++---- .../boot/loader/systemd-boot/systemd-boot.nix | 12 + nixos/tests/systemd-boot.nix | 352 +++++++++++++----- 3 files changed, 495 insertions(+), 178 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 2804aecbddb5..b2873966619c 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -11,7 +11,8 @@ import sys import tempfile import warnings import json -from typing import NamedTuple, Any, Sequence +import glob +from typing import NamedTuple, Any, Sequence, Type from dataclasses import dataclass from pathlib import Path @@ -36,6 +37,8 @@ GRACEFUL = "@graceful@" COPY_EXTRA_FILES = "@copyExtraFiles@" CHECK_MOUNTPOINTS = "@checkMountpoints@" STORE_DIR = "@storeDir@" +BOOT_COUNTING_TRIES = "@bootCountingTries@" +BOOT_COUNTING = "@bootCounting@" == "True" @dataclass @@ -53,6 +56,135 @@ class BootSpec: initrdSecrets: str | None = None # noqa: N815 +@dataclass +class Entry: + profile: str | None + generation_number: int + specialisation: str | None + + @classmethod + def from_path(cls: Type["Entry"], path: Path) -> "Entry": + filename = path.name + # Matching nixos-$profile-generation-*.conf + rex_profile = re.compile(r"^nixos-(.*)-generation-.*\.conf$") + # Matching nixos*-generation-$number*.conf + rex_generation = re.compile(r"^nixos.*-generation-([0-9]+).*\.conf$") + # Matching nixos*-generation-$number-specialisation-$specialisation_name*.conf + rex_specialisation = re.compile( + r"^nixos.*-generation-([0-9]+)-specialisation-([a-zA-Z0-9]+).*\.conf$" + ) + profile = ( + rex_profile.sub(r"\1", filename) if rex_profile.match(filename) else None + ) + specialisation = ( + rex_specialisation.sub(r"\2", filename) + if rex_specialisation.match(filename) + else None + ) + try: + generation_number = int(rex_generation.sub(r"\1", filename)) + except ValueError: + raise + return cls(profile, generation_number, specialisation) + + +@dataclass +class DiskEntry: + entry: Entry + default: bool + counters: str | None + title: str | None + description: str | None + kernel: Path + initrd: Path + devicetree: Path | None + kernel_params: str | None + machine_id: str | None + sort_key: str + + @classmethod + def from_path(cls: Type["DiskEntry"], path: Path) -> "DiskEntry": + entry = Entry.from_path(path) + data = path.read_text().splitlines() + if "" in data: + data.remove("") + entry_map = dict(lines.split(" ", 1) for lines in data) + assert "linux" in entry_map + assert "initrd" in entry_map + filename = path.name + # Matching nixos*-generation-*$counters.conf + rex_counters = re.compile(r"^nixos.*-generation-.*(\+\d(-\d)?)\.conf$") + counters = ( + rex_counters.sub(r"\1", filename) if rex_counters.match(filename) else None + ) + + maybe_devicetree_path = entry_map.get("devicetree") + disk_entry = cls( + entry=entry, + default=(entry_map.get("sort-key") == "default"), + counters=counters, + title=entry_map.get("title"), + description=entry_map.get("version"), + kernel=Path(entry_map["linux"]), + initrd=Path(entry_map["initrd"]), + devicetree=Path(maybe_devicetree_path) if maybe_devicetree_path else None, + machine_id=entry_map.get("machine-id"), + sort_key=entry_map.get("sort_key", "nixos"), + kernel_params=entry_map.get("options"), + ) + return disk_entry + + def write(self, sorted_first: str) -> None: + # TODO + # Compute a sort-key sorted before sorted_first + # This will compute something like: nixos -> nixor-default to make sure we come before other nixos entries, + # while allowing users users can pre-pend their own entries before. + default_sort_key = ( + sorted_first[:-1] + chr(ord(sorted_first[-1]) - 1) + "-default" + ) + tmp_path = self.path.with_suffix(".tmp") + with tmp_path.open("w") as f: + # We use "sort-key" to sort the default generation first. + # The "default" string is sorted before "non-default" (alphabetically) + boot_entry = [ + f"title {self.title}" if self.title is not None else None, + f"version {self.description}" if self.description is not None else None, + f"linux {self.kernel}", + f"initrd {self.initrd}", + f"options {self.kernel_params}" + if self.kernel_params is not None + else None, + f"machine-id {self.machine_id}" + if self.machine_id is not None + else None, + f"devicetree /{self.devicetree}" + if self.devicetree is not None + else None, + f"sort-key {default_sort_key if self.default else self.sort_key}", + ] + + f.write("\n".join(filter(None, boot_entry))) + f.flush() + os.fsync(f.fileno()) + tmp_path.rename(self.path) + + @property + def path(self) -> Path: + pieces = [ + "nixos", + self.entry.profile or None, + "generation", + str(self.entry.generation_number), + f"specialisation-{self.entry.specialisation}" + if self.entry.specialisation + else None, + ] + prefix = "-".join(p for p in pieces if p) + return Path( + f"{BOOT_MOUNT_POINT}/loader/entries/{prefix}{self.counters if self.counters else ''}.conf" + ) + + libc = ctypes.CDLL("libc.so.6") FILE = None | int @@ -72,7 +204,9 @@ class SystemIdentifier(NamedTuple): def copy_if_not_exists(source: Path, dest: Path) -> None: if not dest.exists(): - tmpfd, tmppath = tempfile.mkstemp(dir=dest.parent, prefix=dest.name, suffix='.tmp.') + tmpfd, tmppath = tempfile.mkstemp( + dir=dest.parent, prefix=dest.name, suffix=".tmp." + ) shutil.copyfile(source, tmppath) os.fsync(tmpfd) shutil.move(tmppath, dest) @@ -97,38 +231,14 @@ def system_dir( return d -BOOT_ENTRY = """title {title} -sort-key {sort_key} -version Generation {generation} {description} -linux {kernel} -initrd {initrd} -options {kernel_params} -""" - - -def generation_conf_filename( - profile: str | None, generation: int, specialisation: str | None -) -> str: - pieces = [ - "nixos", - profile or None, - "generation", - str(generation), - f"specialisation-{specialisation}" if specialisation else None, - ] - return "-".join(p for p in pieces if p) + ".conf" - - -def write_loader_conf( - profile: str | None, generation: int, specialisation: str | None -) -> None: +def write_loader_conf(profile: str | None) -> None: tmp = LOADER_CONF.with_suffix(".tmp") with tmp.open("x") as f: f.write(f"timeout {TIMEOUT}\n") - f.write( - "default %s\n" - % generation_conf_filename(profile, generation, specialisation) - ) + if profile: + f.write("default nixos-%s-generation-*\n" % profile) + else: + f.write("default nixos-generation-*\n") if not EDITOR: f.write("editor 0\n") if REBOOT_FOR_BITLOCKER: @@ -139,6 +249,23 @@ def write_loader_conf( os.rename(tmp, LOADER_CONF) +def scan_entries() -> list[DiskEntry]: + """ + Scan all entries in $ESP/loader/entries/* + Does not support Type 2 entries as we do not support them for now. + Returns a generator of Entry. + """ + entries = [] + for path in Path(f"{EFI_SYS_MOUNT_POINT}/loader/entries/").glob( + "nixos*-generation-[1-9]*.conf" + ): + try: + entries.append(DiskEntry.from_path(path)) + except ValueError: + continue + return entries + + def get_bootspec(profile: str | None, generation: int) -> BootSpec: system_directory = system_dir(profile, generation, None) boot_json_path = (system_directory / "boot.json").resolve() @@ -210,6 +337,8 @@ def write_entry( specialisation: str | None, machine_id: str | None, bootspec: BootSpec, + entries: list[DiskEntry], + sorted_first: str, current: bool, ) -> None: if specialisation: @@ -244,37 +373,35 @@ def write_entry( "or renamed a file in `boot.initrd.secrets`", file=sys.stderr, ) - entry_file = ( - BOOT_MOUNT_POINT - / "loader/entries" - / generation_conf_filename(profile, generation, specialisation) - ) - tmp_path = entry_file.with_suffix(".tmp") kernel_params = "init=%s " % bootspec.init kernel_params = kernel_params + " ".join(bootspec.kernelParams) build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) build_date = datetime.datetime.fromtimestamp(build_time).strftime("%F") - with tmp_path.open("w") as f: - f.write( - BOOT_ENTRY.format( - title=title, - sort_key=bootspec.sortKey, - generation=generation, - kernel=f"/{kernel}", - initrd=f"/{initrd}", - kernel_params=kernel_params, - description=f"{bootspec.label}, built on {build_date}", - ) - ) - if machine_id is not None: - f.write("machine-id %s\n" % machine_id) - if devicetree is not None: - f.write(f"devicetree /{devicetree}\n") - f.flush() - os.fsync(f.fileno()) - tmp_path.rename(entry_file) + counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else "" + entry = Entry(profile, generation, specialisation) + # We check if the entry we are writing is already on disk + # and we update its "default entry" status + for entry_on_disk in entries: + if entry == entry_on_disk.entry: + entry_on_disk.default = current + entry_on_disk.write(sorted_first) + return + + DiskEntry( + entry=entry, + title=title, + kernel=kernel, + initrd=initrd, + devicetree=devicetree, + counters=counters, + kernel_params=kernel_params, + machine_id=machine_id, + description=f"Generation {generation} {bootspec.label}, built on {build_date}", + sort_key=bootspec.sortKey, + default=current, + ).write(sorted_first) def get_generations(profile: str | None = None) -> list[SystemIdentifier]: @@ -301,11 +428,9 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]: return configurations[-configurationLimit:] -def remove_old_entries(gens: list[SystemIdentifier]) -> None: - rex_profile = re.compile(r"^nixos-(.*)-generation-.*\.conf$") - rex_generation = re.compile( - r"^nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$" - ) +def remove_old_entries( + gens: list[SystemIdentifier], disk_entries: list[DiskEntry] +) -> None: known_paths = [] for gen in gens: bootspec = get_bootspec(gen.profile, gen.generation) @@ -313,20 +438,16 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None: known_paths.append(copy_from_file(bootspec.initrd, True).name) if bootspec.devicetree is not None: known_paths.append(copy_from_file(bootspec.devicetree, True).name) - for path in (BOOT_MOUNT_POINT / "loader/entries").glob( - "nixos*-generation-[1-9]*.conf", case_sensitive=False - ): - if rex_profile.match(path.name): - prof = rex_profile.sub(r"\1", path.name) - else: - prof = None - try: - gen_number = int(rex_generation.sub(r"\1", path.name)) - except ValueError: - continue - if (prof, gen_number, None) not in gens: - path.unlink() - for path in (BOOT_MOUNT_POINT / NIXOS_DIR).iterdir(): + + for disk_entry in disk_entries: + if ( + disk_entry.entry.profile, + disk_entry.entry.generation_number, + None, + ) not in gens: + os.unlink(disk_entry.path) + for path_name in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"): + path = Path(path_name) if path.name not in known_paths and not path.is_dir(): path.unlink() @@ -444,13 +565,37 @@ def install_bootloader(args: argparse.Namespace) -> None: for profile in get_profiles(): gens += get_generations(profile) - remove_old_entries(gens) + entries = scan_entries() + remove_old_entries(gens, entries) + # Compute the sort-key that will be sorted first. + sorted_first = "" + for gen in gens: + try: + bootspec = get_bootspec(gen.profile, gen.generation) + if bootspec.sortKey < sorted_first or sorted_first == "": + sorted_first = bootspec.sortKey + except OSError as e: + # See https://github.com/NixOS/nixpkgs/issues/114552 + if e.errno == errno.EINVAL: + profile = ( + f"profile '{gen.profile}'" if gen.profile else "default profile" + ) + print( + "ignoring {} in the list of boot entries because of the following error:\n{}".format( + profile, e + ), + file=sys.stderr, + ) + else: + raise e for gen in gens: try: bootspec = get_bootspec(gen.profile, gen.generation) is_default = Path(bootspec.init).parent == Path(args.default_config) - write_entry(*gen, machine_id, bootspec, current=is_default) + write_entry( + *gen, machine_id, bootspec, entries, sorted_first, current=is_default + ) for specialisation in bootspec.specialisations.keys(): write_entry( gen.profile, @@ -458,10 +603,16 @@ def install_bootloader(args: argparse.Namespace) -> None: specialisation, machine_id, bootspec, - current=is_default, + entries, + sorted_first, + current=( + is_default + and bootspec.specialisations[specialisation].sortKey + == bootspec.sortKey + ), ) if is_default: - write_loader_conf(*gen) + write_loader_conf(gen.profile) except OSError as e: # See https://github.com/NixOS/nixpkgs/issues/114552 if e.errno == errno.EINVAL: diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix index 28ba374272f3..f7f7daf8a331 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -97,6 +97,9 @@ let '') cfg.extraEntries )} ''; + + bootCountingTries = cfg.bootCounting.tries; + bootCounting = if cfg.bootCounting.enable then "True" else "False"; }; }; @@ -417,6 +420,15 @@ in ''; }; + bootCounting = { + enable = mkEnableOption "automatic boot assessment"; + tries = mkOption { + default = 3; + type = types.int; + description = "number of tries each entry should start with"; + }; + }; + rebootForBitlocker = mkOption { default = false; diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index 53f8db7a808c..3b205b20873d 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -14,6 +14,8 @@ let boot.loader.efi.canTouchEfiVariables = true; environment.systemPackages = [ pkgs.efibootmgr ]; system.switch.enable = true; + # Needed for machine-id to be persisted between reboots + environment.etc."machine-id".text = "00000000000000000000000000000000"; }; commonXbootldr = @@ -90,8 +92,111 @@ let # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name ''; + # Check that we are booting the default entry and not the generation with largest version number + defaultEntry = + { + lib, + pkgs, + withBootCounting ? false, + ... + }: + runTest { + name = "systemd-boot-default-entry" + lib.optionalString withBootCounting "-with-boot-counting"; + meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + + nodes = { + machine = + { nodes, ... }: + { + imports = [ common ]; + system.extraDependencies = [ nodes.other_machine.system.build.toplevel ]; + boot.loader.systemd-boot.bootCounting.enable = withBootCounting; + }; + + other_machine = { + imports = [ common ]; + boot.loader.systemd-boot.bootCounting.enable = withBootCounting; + environment.systemPackages = [ pkgs.hello ]; + }; + }; + testScript = + { nodes, ... }: + let + orig = nodes.machine.system.build.toplevel; + other = nodes.other_machine.system.build.toplevel; + in + '' + orig = "${orig}" + other = "${other}" + + def check_current_system(system_path): + machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') + + check_current_system(orig) + + # Switch to other configuration + machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${other}") + machine.succeed(f"{other}/bin/switch-to-configuration boot") + # Rollback, default entry is now generation 1 + machine.succeed("nix-env -p /nix/var/nix/profiles/system --rollback") + machine.succeed(f"{orig}/bin/switch-to-configuration boot") + machine.shutdown() + machine.start() + machine.wait_for_unit("multi-user.target") + # Check that we booted generation 1 (default) + # even though generation 2 comes first in alphabetical order + check_current_system(orig) + ''; + }; + garbage-collect-entry = + { + withBootCounting ? false, + ... + }: + runTest ( + { lib, ... }: + { + name = + "systemd-boot-garbage-collect-entry" + lib.optionalString withBootCounting "-with-boot-counting"; + meta.maintainers = with lib.maintainers; [ julienmalka ]; + + nodes = { + inherit common; + machine = + { nodes, ... }: + { + imports = [ common ]; + + boot.loader.systemd-boot.bootCounting.enable = withBootCounting; + + # These are configs for different nodes, but we'll use them here in `machine` + system.extraDependencies = [ + nodes.common.system.build.toplevel + ]; + }; + }; + + testScript = + { nodes, ... }: + let + baseSystem = nodes.common.system.build.toplevel; + in + '' + machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") + machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") + # At this point generation 1 has already been marked as good so we reintroduce counters artificially + ${lib.optionalString withBootCounting '' + machine.succeed("mv /boot/loader/entries/nixos-generation-1.conf /boot/loader/entries/nixos-generation-1+3.conf") + ''} + machine.succeed("${baseSystem}/bin/switch-to-configuration boot") + machine.fail("test -e /boot/loader/entries/nixos-generation-1*") + machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf") + ''; + } + ); in { + inherit defaultEntry; basic = runTest ( { lib, ... }: { @@ -108,7 +213,10 @@ in machine.wait_for_unit("multi-user.target") machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf") + out = machine.succeed("cat /boot/loader/entries/nixos-generation-1.conf") + print(out) + # our sort-key will uses r to sort before nixos + machine.succeed("grep 'sort-key nixor-default' /boot/loader/entries/nixos-generation-1.conf") # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. @@ -224,33 +332,31 @@ in }; }; - testScript = - { nodes, ... }: - '' - machine.start() - machine.wait_for_unit("multi-user.target") + testScript = '' + machine.start() + machine.wait_for_unit("multi-user.target") - machine.succeed( - "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - machine.succeed( - "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - machine.succeed( - "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - '' - + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' - machine.succeed( - r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - ''; + machine.succeed( + "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + machine.succeed( + "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + machine.succeed( + "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + '' + + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' + machine.succeed( + r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + ''; } ); # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI" fallback = runTest ( - { pkgs, lib, ... }: + { lib, ... }: { name = "systemd-boot-fallback"; meta.maintainers = with lib.maintainers; [ @@ -259,7 +365,7 @@ in ]; nodes.machine = - { pkgs, lib, ... }: + { lib, ... }: { imports = [ common ]; boot.loader.efi.canTouchEfiVariables = lib.mkForce false; @@ -333,12 +439,10 @@ in name = "systemd-boot-memtest86"; meta.maintainers = with lib.maintainers; [ julienmalka ]; - nodes.machine = - { pkgs, lib, ... }: - { - imports = [ common ]; - boot.loader.systemd-boot.memtest86.enable = true; - }; + nodes.machine = { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + }; testScript = '' machine.succeed("test -e /boot/loader/entries/memtest86.conf") @@ -353,12 +457,10 @@ in name = "systemd-boot-netbootxyz"; meta.maintainers = with lib.maintainers; [ julienmalka ]; - nodes.machine = - { pkgs, lib, ... }: - { - imports = [ common ]; - boot.loader.systemd-boot.netbootxyz.enable = true; - }; + nodes.machine = { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; testScript = '' machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") @@ -443,13 +545,11 @@ in name = "systemd-boot-memtest-sortkey"; meta.maintainers = with lib.maintainers; [ julienmalka ]; - nodes.machine = - { pkgs, lib, ... }: - { - imports = [ common ]; - boot.loader.systemd-boot.memtest86.enable = true; - boot.loader.systemd-boot.memtest86.sortKey = "apple"; - }; + nodes.machine = { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + boot.loader.systemd-boot.memtest86.sortKey = "apple"; + }; testScript = '' machine.succeed("test -e /boot/loader/entries/memtest86.conf") @@ -466,12 +566,10 @@ in name = "systemd-boot-entry-filename-xbootldr"; meta.maintainers = with lib.maintainers; [ sdht0 ]; - nodes.machine = - { pkgs, lib, ... }: - { - imports = [ commonXbootldr ]; - boot.loader.systemd-boot.memtest86.enable = true; - }; + nodes.machine = { + imports = [ commonXbootldr ]; + boot.loader.systemd-boot.memtest86.enable = true; + }; testScript = { nodes, ... }: @@ -494,16 +592,14 @@ in name = "systemd-boot-extra-entries"; meta.maintainers = with lib.maintainers; [ julienmalka ]; - nodes.machine = - { pkgs, lib, ... }: - { - imports = [ common ]; - boot.loader.systemd-boot.extraEntries = { - "banana.conf" = '' - title banana - ''; - }; + nodes.machine = { + imports = [ common ]; + boot.loader.systemd-boot.extraEntries = { + "banana.conf" = '' + title banana + ''; }; + }; testScript = '' machine.succeed("test -e /boot/loader/entries/banana.conf") @@ -519,7 +615,7 @@ in meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes.machine = - { pkgs, lib, ... }: + { pkgs, ... }: { imports = [ common ]; boot.loader.systemd-boot.extraFiles = { @@ -558,12 +654,10 @@ in ]; }; - with_netbootxyz = - { pkgs, ... }: - { - imports = [ common ]; - boot.loader.systemd-boot.netbootxyz.enable = true; - }; + with_netbootxyz = { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; }; testScript = @@ -602,41 +696,6 @@ in } ); - garbage-collect-entry = runTest ( - { lib, ... }: - { - name = "systemd-boot-garbage-collect-entry"; - meta.maintainers = with lib.maintainers; [ julienmalka ]; - - nodes = { - inherit common; - machine = - { pkgs, nodes, ... }: - { - imports = [ common ]; - - # These are configs for different nodes, but we'll use them here in `machine` - system.extraDependencies = [ - nodes.common.system.build.toplevel - ]; - }; - }; - - testScript = - { nodes, ... }: - let - baseSystem = nodes.common.system.build.toplevel; - in - '' - machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") - machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") - machine.succeed("${baseSystem}/bin/switch-to-configuration boot") - machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf") - ''; - } - ); - no-bootspec = runTest ( { lib, ... }: { @@ -654,4 +713,99 @@ in ''; } ); + + bootCounting = + let + baseConfig = { + imports = [ common ]; + boot.loader.systemd-boot.bootCounting.enable = true; + boot.loader.systemd-boot.bootCounting.trials = 2; + }; + in + { pkgs, ... }: + runTest { + name = "systemd-boot-counting"; + meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + + nodes = { + machine = + { nodes, ... }: + { + imports = [ baseConfig ]; + system.extraDependencies = [ nodes.bad_machine.system.build.toplevel ]; + }; + + bad_machine = { + imports = [ baseConfig ]; + + systemd.services."failing" = { + script = "exit 1"; + requiredBy = [ "boot-complete.target" ]; + before = [ "boot-complete.target" ]; + serviceConfig.Type = "oneshot"; + }; + }; + }; + testScript = + { nodes, ... }: + let + orig = nodes.machine.system.build.toplevel; + bad = nodes.bad_machine.system.build.toplevel; + in + '' + orig = "${orig}" + bad = "${bad}" + + def check_current_system(system_path): + machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') + + # Ensure we booted using an entry with counters enabled + machine.succeed( + "test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" + ) + + # systemd-bless-boot should have already removed the "+2" suffix from the boot entry + machine.wait_for_unit("systemd-bless-boot.service") + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + check_current_system(orig) + + # Switch to bad configuration + machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${bad}") + machine.succeed(f"{bad}/bin/switch-to-configuration boot") + + # Ensure new bootloader entry has initialized counter + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.succeed("test -e /boot/loader/entries/nixos-generation-2+2.conf") + machine.shutdown() + + machine.start() + machine.wait_for_unit("multi-user.target") + check_current_system(bad) + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.succeed("test -e /boot/loader/entries/nixos-generation-2+1-1.conf") + machine.shutdown() + + machine.start() + machine.wait_for_unit("multi-user.target") + check_current_system(bad) + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf") + machine.shutdown() + + # Should boot back into original configuration + machine.start() + check_current_system(orig) + machine.wait_for_unit("multi-user.target") + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf") + machine.shutdown() + ''; + }; + defaultEntryWithBootCounting = + { lib, pkgs }: + defaultEntry { + inherit lib pkgs; + withBootCounting = true; + }; + garbageCollectEntryWithBootCounting = garbage-collect-entry { withBootCounting = true; }; } From 323ef6c123cca6d60f827141ef6844c3ac1cfab3 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 22 Apr 2026 19:40:31 +0200 Subject: [PATCH 31/55] nixos/tests/systemd-boot: use a valid machine-id dbus-broker (now the default since #512050) calls sd_id128_get_machine() which returns -ENOMEDIUM for an all-zero machine-id, causing it to crash-loop and the test to hang on multi-user.target. --- nixos/tests/systemd-boot.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index 3b205b20873d..1ccd09dcee6d 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -14,8 +14,10 @@ let boot.loader.efi.canTouchEfiVariables = true; environment.systemPackages = [ pkgs.efibootmgr ]; system.switch.enable = true; - # Needed for machine-id to be persisted between reboots - environment.etc."machine-id".text = "00000000000000000000000000000000"; + # Needed for machine-id to be persisted between reboots. + # Must be a valid (non-zero) ID, otherwise sd_id128_get_machine() + # returns -ENOMEDIUM and dbus-broker refuses to start. + environment.etc."machine-id".text = "1234567890abcdef1234567890abcdef\n"; }; commonXbootldr = From b4c278c06b2ab8ee3e9efdf26659247d1ea081f8 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Thu, 25 Sep 2025 17:40:14 +0200 Subject: [PATCH 32/55] nixos/systemd-boot-builder: store boot loader configs using content hashing Co-Authored-By: AkechiShiro <14914796+AkechiShiro@users.noreply.github.com> --- .../manual/release-notes/rl-2611.section.md | 2 + .../systemd-boot/systemd-boot-builder.py | 434 +++++++-------- .../boot/loader/systemd-boot/systemd-boot.nix | 17 +- nixos/tests/all-tests.nix | 5 +- nixos/tests/systemd-boot.nix | 523 +++++++++++------- 5 files changed, 555 insertions(+), 426 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2611.section.md b/nixos/doc/manual/release-notes/rl-2611.section.md index bb2058d6b4ac..98548f040816 100644 --- a/nixos/doc/manual/release-notes/rl-2611.section.md +++ b/nixos/doc/manual/release-notes/rl-2611.section.md @@ -26,4 +26,6 @@ +- `boot.loader.systemd-boot` gained support for [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/) via the new [`boot.loader.systemd-boot.bootCounting`](#opt-boot.loader.systemd-boot.bootCounting.enable) options, allowing automatic detection of and recovery from bad NixOS generations. As part of this change, boot loader entries on the ESP/XBOOTLDR partition are now named `nixos-.conf` instead of `nixos-generation-.conf`; existing entries are migrated automatically on the next `nixos-rebuild boot`/`switch`. + - The `newuidmap` and `newgidmap` security wrappers are now installed with `cap_setuid`/`cap_setgid` file capabilities instead of the setuid-root bit, matching shadow's `--with-fcaps` install mode and other major distributions. Rootless containers (podman, docker-rootless, unprivileged user namespaces) are unaffected. The only behavioural change is that mapping host uid 0 via `/etc/subuid` (which NixOS never configures by default) additionally requires `cap_setfcap`; users who explicitly grant uid 0 in a subuid range can restore the previous behaviour with `security.wrappers.newuidmap.capabilities = lib.mkForce "cap_setuid,cap_setfcap+ep";`. diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index b2873966619c..58bbb5c87921 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -3,6 +3,7 @@ import argparse import ctypes import datetime import errno +import hashlib import os import re import shutil @@ -11,8 +12,7 @@ import sys import tempfile import warnings import json -import glob -from typing import NamedTuple, Any, Sequence, Type +from typing import NamedTuple, Any, Sequence from dataclasses import dataclass from pathlib import Path @@ -32,8 +32,8 @@ NIX = "@nix@" SYSTEMD = "@systemd@" CONFIGURATION_LIMIT = int("@configurationLimit@") REBOOT_FOR_BITLOCKER = bool("@rebootForBitlocker@") -CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@" -GRACEFUL = "@graceful@" +CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@" == "1" +GRACEFUL = "@graceful@" == "1" COPY_EXTRA_FILES = "@copyExtraFiles@" CHECK_MOUNTPOINTS = "@checkMountpoints@" STORE_DIR = "@storeDir@" @@ -41,7 +41,7 @@ BOOT_COUNTING_TRIES = "@bootCountingTries@" BOOT_COUNTING = "@bootCounting@" == "True" -@dataclass +@dataclass(frozen=True) class BootSpec: init: Path initrd: Path @@ -56,44 +56,31 @@ class BootSpec: initrdSecrets: str | None = None # noqa: N815 -@dataclass +@dataclass(frozen=True) +class GcRoot: + prefix: Path | None + path: Path | None + + @staticmethod + def from_prefix(prefix: Path) -> "GcRoot": + return GcRoot(prefix=prefix, path=None) + + @staticmethod + def from_path(path: Path) -> "GcRoot": + return GcRoot(prefix=None, path=path) + + +@dataclass(frozen=True) class Entry: profile: str | None generation_number: int specialisation: str | None - @classmethod - def from_path(cls: Type["Entry"], path: Path) -> "Entry": - filename = path.name - # Matching nixos-$profile-generation-*.conf - rex_profile = re.compile(r"^nixos-(.*)-generation-.*\.conf$") - # Matching nixos*-generation-$number*.conf - rex_generation = re.compile(r"^nixos.*-generation-([0-9]+).*\.conf$") - # Matching nixos*-generation-$number-specialisation-$specialisation_name*.conf - rex_specialisation = re.compile( - r"^nixos.*-generation-([0-9]+)-specialisation-([a-zA-Z0-9]+).*\.conf$" - ) - profile = ( - rex_profile.sub(r"\1", filename) if rex_profile.match(filename) else None - ) - specialisation = ( - rex_specialisation.sub(r"\2", filename) - if rex_specialisation.match(filename) - else None - ) - try: - generation_number = int(rex_generation.sub(r"\1", filename)) - except ValueError: - raise - return cls(profile, generation_number, specialisation) - -@dataclass +@dataclass(frozen=True) class DiskEntry: entry: Entry - default: bool counters: str | None - title: str | None description: str | None kernel: Path initrd: Path @@ -102,87 +89,84 @@ class DiskEntry: machine_id: str | None sort_key: str - @classmethod - def from_path(cls: Type["DiskEntry"], path: Path) -> "DiskEntry": - entry = Entry.from_path(path) - data = path.read_text().splitlines() - if "" in data: - data.remove("") - entry_map = dict(lines.split(" ", 1) for lines in data) - assert "linux" in entry_map - assert "initrd" in entry_map - filename = path.name - # Matching nixos*-generation-*$counters.conf - rex_counters = re.compile(r"^nixos.*-generation-.*(\+\d(-\d)?)\.conf$") - counters = ( - rex_counters.sub(r"\1", filename) if rex_counters.match(filename) else None + @property + def title(self) -> str: + return "{name}{profile}{specialisation}".format( + name=DISTRO_NAME, + profile=" [" + self.entry.profile + "]" if self.entry.profile else "", + specialisation=" (%s)" % self.entry.specialisation + if self.entry.specialisation + else "", ) - maybe_devicetree_path = entry_map.get("devicetree") - disk_entry = cls( - entry=entry, - default=(entry_map.get("sort-key") == "default"), - counters=counters, - title=entry_map.get("title"), - description=entry_map.get("version"), - kernel=Path(entry_map["linux"]), - initrd=Path(entry_map["initrd"]), - devicetree=Path(maybe_devicetree_path) if maybe_devicetree_path else None, - machine_id=entry_map.get("machine-id"), - sort_key=entry_map.get("sort_key", "nixos"), - kernel_params=entry_map.get("options"), - ) - return disk_entry + def serialise(self) -> str: + boot_entry = [ + f"title {self.title}", + f"version {self.description}" if self.description is not None else None, + f"linux /{self.kernel}", + f"initrd /{self.initrd}", + f"options {self.kernel_params}" if self.kernel_params is not None else None, + f"machine-id {self.machine_id}" if self.machine_id is not None else None, + f"devicetree /{self.devicetree}" if self.devicetree is not None else None, + f"sort-key {self.sort_key}", + ] + return "\n".join(filter(None, boot_entry)) - def write(self, sorted_first: str) -> None: - # TODO - # Compute a sort-key sorted before sorted_first - # This will compute something like: nixos -> nixor-default to make sure we come before other nixos entries, - # while allowing users users can pre-pend their own entries before. - default_sort_key = ( - sorted_first[:-1] + chr(ord(sorted_first[-1]) - 1) + "-default" - ) + def write(self) -> GcRoot: + # Check first if the file already exists + for e in os.scandir(path=BOOT_MOUNT_POINT / "loader" / "entries"): + match = re.fullmatch( + rf"{self.path_prefix}(\+[0-9]+(-[0-9]+)?)?\.conf", e.name + ) + if match: + # Check that the contents match the hash + with open(e.path, "r") as f: + hash = hashlib.sha256(f.read().encode("utf-8")).hexdigest() + if hash == self.content_hash: + # The contents match, we are done, there is nothing to write + return GcRoot.from_prefix( + BOOT_MOUNT_POINT / "loader" / "entries" / self.path_prefix + ) + + # We didn't find a matching file, so we'll create one tmp_path = self.path.with_suffix(".tmp") with tmp_path.open("w") as f: - # We use "sort-key" to sort the default generation first. - # The "default" string is sorted before "non-default" (alphabetically) - boot_entry = [ - f"title {self.title}" if self.title is not None else None, - f"version {self.description}" if self.description is not None else None, - f"linux {self.kernel}", - f"initrd {self.initrd}", - f"options {self.kernel_params}" - if self.kernel_params is not None - else None, - f"machine-id {self.machine_id}" - if self.machine_id is not None - else None, - f"devicetree /{self.devicetree}" - if self.devicetree is not None - else None, - f"sort-key {default_sort_key if self.default else self.sort_key}", - ] + boot_entry = self.serialise() - f.write("\n".join(filter(None, boot_entry))) + f.write(boot_entry) f.flush() os.fsync(f.fileno()) tmp_path.rename(self.path) + return GcRoot.from_prefix( + BOOT_MOUNT_POINT / "loader" / "entries" / self.path_prefix + ) + + @property + def content_hash(self) -> str: + return hashlib.sha256(self.serialise().encode("utf-8")).hexdigest() + + @property + def path_prefix(self) -> str: + return "-".join( + p + for p in [ + "nixos", + self.content_hash, + ] + if p + ) @property def path(self) -> Path: - pieces = [ - "nixos", - self.entry.profile or None, - "generation", - str(self.entry.generation_number), - f"specialisation-{self.entry.specialisation}" - if self.entry.specialisation - else None, - ] - prefix = "-".join(p for p in pieces if p) - return Path( - f"{BOOT_MOUNT_POINT}/loader/entries/{prefix}{self.counters if self.counters else ''}.conf" - ) + return BOOT_MOUNT_POINT / "loader" / "entries" / self.filename + + @property + def filename(self) -> str: + return f"{self.path_prefix}{self.counters if self.counters else ''}.conf" + + @property + def bootctl_id(self) -> str: + return f"{self.path_prefix}.conf" libc = ctypes.CDLL("libc.so.6") @@ -193,7 +177,7 @@ FILE = None | int def run( cmd: Sequence[str | Path], stdout: FILE = None ) -> subprocess.CompletedProcess[str]: - return subprocess.run(cmd, check=True, text=True, stdout=stdout) + return subprocess.run(cmd, check=True, text=True, stdout=stdout, stderr=sys.stderr) class SystemIdentifier(NamedTuple): @@ -231,14 +215,25 @@ def system_dir( return d -def write_loader_conf(profile: str | None) -> None: +def write_loader_conf(default_entry_id: str | None) -> None: tmp = LOADER_CONF.with_suffix(".tmp") with tmp.open("x") as f: f.write(f"timeout {TIMEOUT}\n") - if profile: - f.write("default nixos-%s-generation-*\n" % profile) + if default_entry_id is None: + # No generation matched the requested default config; fall back to + # the newest entry as determined by Boot Loader Spec sorting. + f.write("default nixos-*\n") + elif BOOT_COUNTING: + # `preferred` (systemd-boot >= 260) honours boot assessment, so a + # generation that exhausted its boot counter is skipped and we fall + # through to `default`. systemd-boot sorts entries with + # tries_left == 0 to the end of the list and resolves the `default` + # glob against that order, so `nixos-*` yields the newest entry that + # is not bad, or a bad one only if every nixos entry is bad. + f.write(f"preferred {default_entry_id}\n") + f.write("default nixos-*\n") else: - f.write("default nixos-generation-*\n") + f.write(f"default {default_entry_id}\n") if not EDITOR: f.write("editor 0\n") if REBOOT_FOR_BITLOCKER: @@ -249,23 +244,6 @@ def write_loader_conf(profile: str | None) -> None: os.rename(tmp, LOADER_CONF) -def scan_entries() -> list[DiskEntry]: - """ - Scan all entries in $ESP/loader/entries/* - Does not support Type 2 entries as we do not support them for now. - Returns a generator of Entry. - """ - entries = [] - for path in Path(f"{EFI_SYS_MOUNT_POINT}/loader/entries/").glob( - "nixos*-generation-[1-9]*.conf" - ): - try: - entries.append(DiskEntry.from_path(path)) - except ValueError: - continue - return entries - - def get_bootspec(profile: str | None, generation: int) -> BootSpec: system_directory = system_dir(profile, generation, None) boot_json_path = (system_directory / "boot.json").resolve() @@ -316,7 +294,7 @@ def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec: ) -def copy_from_file(file: Path, dry_run: bool = False) -> Path: +def copy_from_file(file: Path) -> Path: """ Copy a file to the boot filesystem (XBOOTLDR if in use, otherwise ESP), basing the destination filename on the store path that's being copied from. Return the destination path, relative to the boot filesystem mountpoint. """ @@ -326,8 +304,7 @@ def copy_from_file(file: Path, dry_run: bool = False) -> Path: efi_file_path = NIXOS_DIR / ( f"{suffix}.efi" if suffix == store_subdir else f"{store_subdir}-{suffix}.efi" ) - if not dry_run: - copy_if_not_exists(store_file_path, BOOT_MOUNT_POINT / efi_file_path) + copy_if_not_exists(store_file_path, BOOT_MOUNT_POINT / efi_file_path) return efi_file_path @@ -337,22 +314,41 @@ def write_entry( specialisation: str | None, machine_id: str | None, bootspec: BootSpec, - entries: list[DiskEntry], - sorted_first: str, current: bool, -) -> None: +) -> tuple[DiskEntry, set[GcRoot]]: + gc_roots = set() + if specialisation: bootspec = bootspec.specialisations[specialisation] kernel = copy_from_file(bootspec.kernel) + gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / kernel))) initrd = copy_from_file(bootspec.initrd) + gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / initrd))) devicetree = ( copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None ) + if devicetree is not None: + gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / devicetree))) - title = "{name}{profile}{specialisation}".format( - name=DISTRO_NAME, - profile=" [" + profile + "]" if profile else "", - specialisation=" (%s)" % specialisation if specialisation else "", + kernel_params = "init=%s " % bootspec.init + + kernel_params = kernel_params + " ".join(bootspec.kernelParams) + build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) + build_date = datetime.datetime.fromtimestamp(build_time).strftime("%F") + + counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else None + entry = Entry(profile, generation, specialisation) + + disk_entry = DiskEntry( + entry=entry, + kernel=kernel, + initrd=initrd, + devicetree=devicetree, + counters=counters, + kernel_params=kernel_params, + machine_id=machine_id, + description=f"Generation {generation} {bootspec.label}, built on {build_date}", + sort_key=bootspec.sortKey, ) try: @@ -365,7 +361,7 @@ def write_entry( else: print( "warning: failed to create initrd secrets " - f'for "{title} - Configuration {generation}", an older generation', + f'for "{disk_entry.title} - Configuration {generation}", an older generation', file=sys.stderr, ) print( @@ -373,35 +369,9 @@ def write_entry( "or renamed a file in `boot.initrd.secrets`", file=sys.stderr, ) - kernel_params = "init=%s " % bootspec.init - kernel_params = kernel_params + " ".join(bootspec.kernelParams) - build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) - build_date = datetime.datetime.fromtimestamp(build_time).strftime("%F") - - counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else "" - entry = Entry(profile, generation, specialisation) - # We check if the entry we are writing is already on disk - # and we update its "default entry" status - for entry_on_disk in entries: - if entry == entry_on_disk.entry: - entry_on_disk.default = current - entry_on_disk.write(sorted_first) - return - - DiskEntry( - entry=entry, - title=title, - kernel=kernel, - initrd=initrd, - devicetree=devicetree, - counters=counters, - kernel_params=kernel_params, - machine_id=machine_id, - description=f"Generation {generation} {bootspec.label}, built on {build_date}", - sort_key=bootspec.sortKey, - default=current, - ).write(sorted_first) + gc_roots.add(disk_entry.write()) + return disk_entry, gc_roots def get_generations(profile: str | None = None) -> list[SystemIdentifier]: @@ -428,32 +398,8 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]: return configurations[-configurationLimit:] -def remove_old_entries( - gens: list[SystemIdentifier], disk_entries: list[DiskEntry] -) -> None: - known_paths = [] - for gen in gens: - bootspec = get_bootspec(gen.profile, gen.generation) - known_paths.append(copy_from_file(bootspec.kernel, True).name) - known_paths.append(copy_from_file(bootspec.initrd, True).name) - if bootspec.devicetree is not None: - known_paths.append(copy_from_file(bootspec.devicetree, True).name) - - for disk_entry in disk_entries: - if ( - disk_entry.entry.profile, - disk_entry.entry.generation_number, - None, - ) not in gens: - os.unlink(disk_entry.path) - for path_name in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"): - path = Path(path_name) - if path.name not in known_paths and not path.is_dir(): - path.unlink() - - def cleanup_esp() -> None: - for path in (EFI_SYS_MOUNT_POINT / "loader/entries").glob("nixos*"): + for path in (EFI_SYS_MOUNT_POINT / "loader" / "entries").glob("nixos*"): path.unlink() nixos_dir = EFI_SYS_MOUNT_POINT / NIXOS_DIR if nixos_dir.is_dir(): @@ -492,10 +438,10 @@ def install_bootloader(args: argparse.Namespace) -> None: if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: bootctl_flags.append(f"--boot-path={BOOT_MOUNT_POINT}") - if CAN_TOUCH_EFI_VARIABLES != "1": + if not CAN_TOUCH_EFI_VARIABLES: bootctl_flags.append("--no-variables") - if GRACEFUL == "1": + if GRACEFUL: bootctl_flags.append("--graceful") if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1": @@ -565,15 +511,34 @@ def install_bootloader(args: argparse.Namespace) -> None: for profile in get_profiles(): gens += get_generations(profile) - entries = scan_entries() - remove_old_entries(gens, entries) - # Compute the sort-key that will be sorted first. - sorted_first = "" + gc_roots: set[GcRoot] = set() + + default_config = Path(args.default_config) + default_entry_id: str | None = None + for gen in gens: try: bootspec = get_bootspec(gen.profile, gen.generation) - if bootspec.sortKey < sorted_first or sorted_first == "": - sorted_first = bootspec.sortKey + is_default = Path(bootspec.init).parent == default_config + disk_entry, new_gc_roots = write_entry( + *gen, machine_id, bootspec, current=is_default + ) + gc_roots.update(new_gc_roots) + if is_default: + default_entry_id = disk_entry.bootctl_id + for specialisation_name, specialisation in bootspec.specialisations.items(): + is_default = Path(specialisation.init).parent == default_config + disk_entry, new_gc_roots = write_entry( + gen.profile, + gen.generation, + specialisation_name, + machine_id, + bootspec, + current=is_default, + ) + gc_roots.update(new_gc_roots) + if is_default: + default_entry_id = disk_entry.bootctl_id except OSError as e: # See https://github.com/NixOS/nixpkgs/issues/114552 if e.errno == errno.EINVAL: @@ -589,44 +554,7 @@ def install_bootloader(args: argparse.Namespace) -> None: else: raise e - for gen in gens: - try: - bootspec = get_bootspec(gen.profile, gen.generation) - is_default = Path(bootspec.init).parent == Path(args.default_config) - write_entry( - *gen, machine_id, bootspec, entries, sorted_first, current=is_default - ) - for specialisation in bootspec.specialisations.keys(): - write_entry( - gen.profile, - gen.generation, - specialisation, - machine_id, - bootspec, - entries, - sorted_first, - current=( - is_default - and bootspec.specialisations[specialisation].sortKey - == bootspec.sortKey - ), - ) - if is_default: - write_loader_conf(gen.profile) - except OSError as e: - # See https://github.com/NixOS/nixpkgs/issues/114552 - if e.errno == errno.EINVAL: - profile = ( - f"profile '{gen.profile}'" if gen.profile else "default profile" - ) - print( - "ignoring {} in the list of boot entries because of the following error:\n{}".format( - profile, e - ), - file=sys.stderr, - ) - else: - raise e + write_loader_conf(default_entry_id) if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: # Cleanup any entries in ESP if xbootldrMountPoint is set. @@ -634,6 +562,16 @@ def install_bootloader(args: argparse.Namespace) -> None: # automatically, as we don't have information about the mount point anymore. cleanup_esp() + # Garbage-collect stale kernels/initrds/entries before re-populating extra + # files, so that user-supplied extraEntries (which may also live under + # loader/entries and start with `nixos-`) are not removed again. + garbage_collect(gc_roots) + + remove_extra_files() + run([COPY_EXTRA_FILES]) + + +def remove_extra_files() -> None: extra_files_dir = BOOT_MOUNT_POINT / NIXOS_DIR / ".extra-files" for root, _, files in extra_files_dir.walk(top_down=False): relative_root = root.relative_to(extra_files_dir) @@ -650,7 +588,31 @@ def install_bootloader(args: argparse.Namespace) -> None: extra_files_dir.mkdir(parents=True, exist_ok=True) - run([COPY_EXTRA_FILES]) + +def garbage_collect(gc_roots: set[GcRoot]) -> None: + # Check if a file is in the list of gc roots. + # For prefixes, we need to allow for the potential presence of boot counters. + def has_gc_root(p: Path) -> bool: + for root in gc_roots: + if root.path and root.path == p: + return True + elif root.prefix and re.fullmatch( + rf"{re.escape(str(root.prefix))}(\+[0-9]+(-[0-9]+)?)?\.conf", str(p) + ): + return True + return False + + def delete_path(e: os.DirEntry) -> None: + if e.is_file(follow_symlinks=True) and not has_gc_root(Path(e.path)): + os.remove(e.path) + + for e in os.scandir(BOOT_MOUNT_POINT / NIXOS_DIR): + delete_path(e) + + for e in os.scandir(BOOT_MOUNT_POINT / "loader" / "entries"): + match = re.fullmatch(r"nixos-.+\.conf", e.name) + if match: + delete_path(e) def main() -> None: diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix index f7f7daf8a331..4c3c28b11e90 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -421,11 +421,22 @@ in }; bootCounting = { - enable = mkEnableOption "automatic boot assessment"; + enable = mkEnableOption '' + [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/). + + New boot entries are written with a boot counter in the file name. On + each boot, systemd-boot decrements the counter; once the booted system + reaches `boot-complete.target`, `systemd-bless-boot.service` removes the + counter and marks the entry as good. An entry whose counter reaches zero + is considered bad and will be skipped in favour of an older generation + ''; tries = mkOption { default = 3; - type = types.int; - description = "number of tries each entry should start with"; + type = types.ints.positive; + description = '' + Number of boot attempts a freshly written entry is given before it is + considered bad. + ''; }; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index a4c63fcbc368..663f65595661 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1593,7 +1593,10 @@ in systemd = runTest ./systemd.nix; systemd-analyze = runTest ./systemd-analyze.nix; systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { }; - systemd-boot = import ./systemd-boot.nix { inherit runTest runTestOn; }; + systemd-boot = import ./systemd-boot.nix { + inherit runTest runTestOn; + inherit (pkgs) lib; + }; systemd-bpf = runTest ./systemd-bpf.nix; systemd-capsules = runTest ./systemd-capsules.nix; systemd-confinement = handleTest ./systemd-confinement { }; diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index 1ccd09dcee6d..eb2384a11f03 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -1,10 +1,37 @@ { runTest, runTestOn, + lib, ... }: let + testScriptPreamble = + # python + '' + def check_current_system(system_path): + machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') + + def check_generation(generation: int, tries_left=0, tries_failed=0, specialisation=None) -> list[str]: + if specialisation: + title = f"NixOS ({specialisation})" + else: + title = "NixOS" + + conf_files = machine.succeed( + f"grep --files-with-matches 'version Generation {generation} NixOS' /boot/loader/entries/nixos-*.conf | xargs grep --line-regexp --fixed-strings --files-with-matches 'title {title}'" + ).split("\n") + + suffix = "" + if tries_left: + suffix += f"+{tries_left}" + if tries_failed: + suffix += f"-{tries_failed}" + + assert conf_files[0].endswith(f"{suffix}.conf"), f"Expected {conf_files[0]} to end with {suffix}.conf" + return conf_files + ''; + common = { pkgs, ... }: { @@ -72,39 +99,41 @@ let boot.loader.systemd-boot.xbootldrMountPoint = "/boot"; }; - customDiskImage = nodes: '' - import os - import subprocess - import tempfile + customDiskImage = + nodes: + # python + '' + import os + import subprocess + import tempfile - tmp_disk_image = tempfile.NamedTemporaryFile() + tmp_disk_image = tempfile.NamedTemporaryFile() - subprocess.run([ - "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", - "create", - "-f", - "qcow2", - "-b", - "${nodes.machine.system.build.diskImage}/nixos.qcow2", - "-F", - "qcow2", - tmp_disk_image.name, - ]) + subprocess.run([ + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", + "create", + "-f", + "qcow2", + "-b", + "${nodes.machine.system.build.diskImage}/nixos.qcow2", + "-F", + "qcow2", + tmp_disk_image.name, + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + ''; - # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. - os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name - ''; # Check that we are booting the default entry and not the generation with largest version number defaultEntry = { - lib, - pkgs, withBootCounting ? false, ... }: runTest { name = "systemd-boot-default-entry" + lib.optionalString withBootCounting "-with-boot-counting"; - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { machine = @@ -115,11 +144,13 @@ let boot.loader.systemd-boot.bootCounting.enable = withBootCounting; }; - other_machine = { - imports = [ common ]; - boot.loader.systemd-boot.bootCounting.enable = withBootCounting; - environment.systemPackages = [ pkgs.hello ]; - }; + other_machine = + { pkgs, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.bootCounting.enable = withBootCounting; + environment.systemPackages = [ pkgs.hello ]; + }; }; testScript = { nodes, ... }: @@ -127,13 +158,13 @@ let orig = nodes.machine.system.build.toplevel; other = nodes.other_machine.system.build.toplevel; in + # python '' + ${testScriptPreamble} + orig = "${orig}" other = "${other}" - def check_current_system(system_path): - machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') - check_current_system(orig) # Switch to other configuration @@ -150,6 +181,7 @@ let check_current_system(orig) ''; }; + garbage-collect-entry = { withBootCounting ? false, @@ -170,6 +202,7 @@ let imports = [ common ]; boot.loader.systemd-boot.bootCounting.enable = withBootCounting; + boot.loader.systemd-boot.memtest86.enable = true; # These are configs for different nodes, but we'll use them here in `machine` system.extraDependencies = [ @@ -183,22 +216,33 @@ let let baseSystem = nodes.common.system.build.toplevel; in + # python '' + ${testScriptPreamble} + machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}") machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1") + + conf_file = check_generation(1)[0] + new_conf_file = conf_file.replace(".conf", "+1-3.conf") + # At this point generation 1 has already been marked as good so we reintroduce counters artificially ${lib.optionalString withBootCounting '' - machine.succeed("mv /boot/loader/entries/nixos-generation-1.conf /boot/loader/entries/nixos-generation-1+3.conf") + machine.succeed(f"mv {conf_file} {new_conf_file}") ''} machine.succeed("${baseSystem}/bin/switch-to-configuration boot") - machine.fail("test -e /boot/loader/entries/nixos-generation-1*") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf") + machine.fail( + "grep --files-with-matches 'version Generation 1 NixOS' /boot/loader/entries/nixos-*.conf" + ) + check_generation(2) ''; } ); in { - inherit defaultEntry; + defaultEntry = defaultEntry { }; + garbage-collect-entry = garbage-collect-entry { }; + basic = runTest ( { lib, ... }: { @@ -210,25 +254,26 @@ in nodes.machine = common; - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") + testScript = # python + '' + ${testScriptPreamble} - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - out = machine.succeed("cat /boot/loader/entries/nixos-generation-1.conf") - print(out) - # our sort-key will uses r to sort before nixos - machine.succeed("grep 'sort-key nixor-default' /boot/loader/entries/nixos-generation-1.conf") + machine.start() + machine.wait_for_unit("multi-user.target") - # Ensure we actually booted using systemd-boot - # Magic number is the vendor UUID used by systemd-boot. - machine.succeed( - "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" - ) + conf_file = check_generation(1)[0] - # "bootctl install" should have created an EFI entry - machine.succeed('efibootmgr | grep "Linux Boot Manager"') - ''; + machine.succeed(f"grep 'sort-key nixos' {conf_file}") + + # Ensure we actually booted using systemd-boot + # Magic number is the vendor UUID used by systemd-boot. + machine.succeed( + "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" + ) + + # "bootctl install" should have created an EFI entry + machine.succeed('efibootmgr | grep "Linux Boot Manager"') + ''; } ); @@ -251,6 +296,7 @@ in let efiArch = pkgs.stdenv.hostPlatform.efiArch; in + #python '' machine.start(allow_reboot=True) machine.wait_for_unit("multi-user.target") @@ -279,14 +325,17 @@ in testScript = { nodes, ... }: + #python '' + ${testScriptPreamble} + ${customDiskImage nodes} machine.start() machine.wait_for_unit("multi-user.target") machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + check_generation(1) # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. @@ -334,25 +383,28 @@ in }; }; - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") + testScript = + # python + '' + ${testScriptPreamble} - machine.succeed( - "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - machine.succeed( - "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - machine.succeed( - "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - '' - + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' - machine.succeed( - r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - ''; + machine.start() + machine.wait_for_unit("multi-user.target") + + conf_files = check_generation(1, specialisation="something") + machine.succeed( + f"grep --fixed-strings --line-regexp 'sort-key something' {" ".join(conf_files)}" + ) + + ${lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 + #python + '' + machine.succeed( + fr"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' {" ".join(conf_files)}" + ) + '' + } + ''; } ); @@ -373,21 +425,25 @@ in boot.loader.efi.canTouchEfiVariables = lib.mkForce false; }; - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") + testScript = + # python + '' + ${testScriptPreamble} - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.start() + machine.wait_for_unit("multi-user.target") - # Ensure we actually booted using systemd-boot - # Magic number is the vendor UUID used by systemd-boot. - machine.succeed( - "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" - ) + check_generation(1) - # "bootctl install" should _not_ have created an EFI entry - machine.fail('efibootmgr | grep "Linux Boot Manager"') - ''; + # Ensure we actually booted using systemd-boot + # Magic number is the vendor UUID used by systemd-boot. + machine.succeed( + "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" + ) + + # "bootctl install" should _not_ have created an EFI entry + machine.fail('efibootmgr | grep "Linux Boot Manager"') + ''; } ); @@ -403,35 +459,37 @@ in nodes.machine = common; - testScript = '' - machine.succeed("mount -o remount,rw /boot") + testScript = + # python + '' + machine.succeed("mount -o remount,rw /boot") - def switch(): - # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c - machine.succeed( - """ - find /boot -iname '*boot*.efi' -print0 | \ - xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}' - """ - ) - return machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1") + def switch(): + # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c + machine.succeed( + """ + find /boot -iname '*boot*.efi' -print0 | \ + xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}' + """ + ) + return machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1") - output = switch() - assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message" - assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" - assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" + output = switch() + assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message" + assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" + assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" - with subtest("Test that updating works with lowercase bootx64.efi"): - machine.succeed( - # Move to tmp file name first, otherwise mv complains the new location is the same - "mv /boot/EFI/BOOT/BOOTX64.EFI /boot/EFI/BOOT/bootx64.efi.new", - "mv /boot/EFI/BOOT/bootx64.efi.new /boot/EFI/BOOT/bootx64.efi", - ) - output = switch() - assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message" - assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" - assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" - ''; + with subtest("Test that updating works with lowercase bootx64.efi"): + machine.succeed( + # Move to tmp file name first, otherwise mv complains the new location is the same + "mv /boot/EFI/BOOT/BOOTX64.EFI /boot/EFI/BOOT/bootx64.efi.new", + "mv /boot/EFI/BOOT/bootx64.efi.new /boot/EFI/BOOT/bootx64.efi", + ) + output = switch() + assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message" + assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi" + assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI" + ''; } ); @@ -446,10 +504,12 @@ in boot.loader.systemd-boot.memtest86.enable = true; }; - testScript = '' - machine.succeed("test -e /boot/loader/entries/memtest86.conf") - machine.succeed("test -e /boot/efi/memtest86/memtest.efi") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/efi/memtest86/memtest.efi") + ''; } ); @@ -464,10 +524,12 @@ in boot.loader.systemd-boot.netbootxyz.enable = true; }; - testScript = '' - machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") - machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") + machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") + ''; } ); @@ -484,10 +546,12 @@ in boot.loader.systemd-boot.edk2-uefi-shell.enable = true; }; - testScript = '' - machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") - machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") + machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") + ''; } ); @@ -515,29 +579,31 @@ in }; }; - testScript = '' - machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") + testScript = + # python + '' + machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") - machine.succeed("test -e /boot/loader/entries/windows_7.conf") - machine.succeed("test -e /boot/loader/entries/windows_Ten.conf") - machine.succeed("test -e /boot/loader/entries/windows_11.conf") + machine.succeed("test -e /boot/loader/entries/windows_7.conf") + machine.succeed("test -e /boot/loader/entries/windows_Ten.conf") + machine.succeed("test -e /boot/loader/entries/windows_11.conf") - machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf") - machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf") - machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf") + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf") - machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf") - machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf") - machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf") + machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf") - machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf") - machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf") - machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf") + machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf") - machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf") - machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf") - machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf') - ''; + machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf") + machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf') + ''; } ); @@ -553,11 +619,13 @@ in boot.loader.systemd-boot.memtest86.sortKey = "apple"; }; - testScript = '' - machine.succeed("test -e /boot/loader/entries/memtest86.conf") - machine.succeed("test -e /boot/efi/memtest86/memtest.efi") - machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/efi/memtest86/memtest.efi") + machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") + ''; } ); @@ -575,6 +643,7 @@ in testScript = { nodes, ... }: + # python '' ${customDiskImage nodes} @@ -603,10 +672,12 @@ in }; }; - testScript = '' - machine.succeed("test -e /boot/loader/entries/banana.conf") - machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/loader/entries/banana.conf") + machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") + ''; } ); @@ -625,10 +696,12 @@ in }; }; - testScript = '' - machine.succeed("test -e /boot/efi/fruits/tomato.efi") - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") - ''; + testScript = + # python + '' + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + ''; } ); @@ -669,6 +742,7 @@ in baseSystem = nodes.common.system.build.toplevel; finalSystem = nodes.with_netbootxyz.system.build.toplevel; in + # python '' machine.succeed("test -e /boot/efi/fruits/tomato.efi") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") @@ -709,10 +783,12 @@ in boot.bootspec.enable = false; }; - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") - ''; + testScript = + # python + '' + machine.start() + machine.wait_for_unit("multi-user.target") + ''; } ); @@ -720,21 +796,34 @@ in let baseConfig = { imports = [ common ]; - boot.loader.systemd-boot.bootCounting.enable = true; - boot.loader.systemd-boot.bootCounting.trials = 2; + + boot.loader.systemd-boot.bootCounting = { + enable = true; + tries = 2; + }; }; in - { pkgs, ... }: runTest { name = "systemd-boot-counting"; - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + meta.maintainers = with lib.maintainers; [ julienmalka ]; nodes = { machine = { nodes, ... }: { imports = [ baseConfig ]; - system.extraDependencies = [ nodes.bad_machine.system.build.toplevel ]; + system.extraDependencies = [ + nodes.bad_machine.system.build.toplevel + nodes.unused_machine.system.build.toplevel + ]; + }; + + unused_machine = + { pkgs, ... }: + { + imports = [ baseConfig ]; + # Distinguish this closure from `machine` without pulling in extra deps. + environment.systemPackages = [ pkgs.hello ]; }; bad_machine = { @@ -753,13 +842,17 @@ in let orig = nodes.machine.system.build.toplevel; bad = nodes.bad_machine.system.build.toplevel; + unused = nodes.unused_machine.system.build.toplevel; in + # python '' + ${testScriptPreamble} + orig = "${orig}" bad = "${bad}" + unused = "${unused}" - def check_current_system(system_path): - machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"') + machine.start(allow_reboot=True) # Ensure we booted using an entry with counters enabled machine.succeed( @@ -768,46 +861,104 @@ in # systemd-bless-boot should have already removed the "+2" suffix from the boot entry machine.wait_for_unit("systemd-bless-boot.service") - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + conf_file = check_generation(1) check_current_system(orig) - # Switch to bad configuration + print(machine.succeed("cat /boot/loader/entries/*.conf")) + + # Register the bad configuration as generation 2 and another good + # configuration as generation 3, then make generation 2 the default. + # This verifies that `preferred` in loader.conf selects gen 2 even + # though gen 3 sorts higher, and that once gen 2 is marked bad we + # fall back to the newest non-bad entry (gen 3). machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${bad}") + machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${unused}") machine.succeed(f"{bad}/bin/switch-to-configuration boot") - # Ensure new bootloader entry has initialized counter - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2+2.conf") - machine.shutdown() + # Ensure new bootloader entries have initialized counters + check_generation(1) + check_generation(2, 2) + check_generation(3, 2) + + machine.reboot() - machine.start() machine.wait_for_unit("multi-user.target") check_current_system(bad) - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2+1-1.conf") - machine.shutdown() + check_generation(1) + check_generation(2, 1, 1) + check_generation(3, 2) + + machine.reboot() - machine.start() machine.wait_for_unit("multi-user.target") check_current_system(bad) - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf") - machine.shutdown() + check_generation(1) + check_generation(2, 0, 2) + check_generation(3, 2) + + machine.reboot() - # Should boot back into original configuration - machine.start() - check_current_system(orig) machine.wait_for_unit("multi-user.target") - machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") - machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf") - machine.shutdown() + # Gen 2 has exhausted its tries; `preferred` skips it and `default + # nixos-*` resolves to the newest non-bad entry, which is gen 3. + check_current_system(unused) + machine.wait_for_unit("systemd-bless-boot.service") + check_generation(2, 0, 2) + check_generation(3) ''; }; - defaultEntryWithBootCounting = - { lib, pkgs }: - defaultEntry { - inherit lib pkgs; - withBootCounting = true; + + bootCountingSpecialisation = + let + baseConfig = { + imports = [ common ]; + boot.loader.systemd-boot.bootCounting = { + enable = true; + tries = 2; + }; + }; + + specialisationName = "+something+-+that+-+breaks-parsing+-+"; + in + runTest { + name = "systemd-boot-counting-specialisation"; + meta.maintainers = with lib.maintainers; [ julienmalka ]; + + nodes = { + machine = + { nodes, lib, ... }: + { + imports = [ baseConfig ]; + specialisation.${specialisationName}.configuration = { + boot.loader.systemd-boot.sortKey = "something"; + }; + }; + }; + testScript = + { nodes, ... }: + let + orig = nodes.machine.system.build.toplevel; + in + # python + '' + ${testScriptPreamble} + + orig = "${orig}" + + # Ensure we booted using an entry with counters enabled + machine.succeed( + "test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" + ) + + check_generation(1) + check_current_system(orig) + + # Ensure the bootloader entry for the specialisation has initialized the boot counter + check_generation(1, 2, specialisation="${specialisationName}") + ''; }; + + defaultEntryWithBootCounting = defaultEntry { withBootCounting = true; }; + garbageCollectEntryWithBootCounting = garbage-collect-entry { withBootCounting = true; }; } From 1d081050c30f60a87994e688dc97e25ac65738f3 Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Fri, 1 May 2026 13:04:39 -0400 Subject: [PATCH 33/55] nixos/systemd-boot: Separate finding the placement of files from writing files --- .../systemd-boot/systemd-boot-builder.py | 424 +++++++++--------- 1 file changed, 201 insertions(+), 223 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 58bbb5c87921..88c1bac112fa 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -12,7 +12,7 @@ import sys import tempfile import warnings import json -from typing import NamedTuple, Any, Sequence +from typing import NamedTuple, Any, Protocol, Sequence from dataclasses import dataclass from pathlib import Path @@ -56,117 +56,150 @@ class BootSpec: initrdSecrets: str | None = None # noqa: N815 -@dataclass(frozen=True) -class GcRoot: - prefix: Path | None - path: Path | None - - @staticmethod - def from_prefix(prefix: Path) -> "GcRoot": - return GcRoot(prefix=prefix, path=None) - - @staticmethod - def from_path(path: Path) -> "GcRoot": - return GcRoot(prefix=None, path=path) +class WriteBootFile(Protocol): + def write_boot_file(self, path: Path) -> None: ... -@dataclass(frozen=True) -class Entry: +@dataclass +class CopyWriter: + source: Path + + def write_boot_file(self, path: Path) -> None: + with tempfile.NamedTemporaryFile( + mode="wb", + dir=path.parent, + delete=False, + prefix=path.name, + suffix=".tmp", + ) as tmp: + with open(self.source, mode="rb") as source_file: + shutil.copyfileobj(source_file, tmp) + tmp.flush() + os.fsync(tmp.fileno()) + tmp.close() + os.rename(tmp.name, path) + + +@dataclass +class InitrdWithSecretsWriter: + source: Path + initrd_secrets: Path + + def write_boot_file(self, path: Path) -> None: + with tempfile.NamedTemporaryFile( + mode="wb", + dir=path.parent, + delete=False, + prefix=path.name, + suffix=".tmp", + ) as tmp: + with open(self.source, mode="rb") as source_file: + shutil.copyfileobj(source_file, tmp) + tmp.flush() + run([self.initrd_secrets, tmp.name]) + os.fsync(tmp.fileno()) + tmp.close() + os.rename(tmp.name, path) + + +@dataclass +class ContentsWriter: + contents: bytes + + def write_boot_file(self, path: Path) -> None: + with tempfile.NamedTemporaryFile( + mode="wb", + dir=path.parent, + delete=False, + prefix=path.name, + suffix=".tmp", + ) as tmp: + tmp.write(self.contents) + tmp.flush() + os.fsync(tmp.fileno()) + tmp.close() + os.rename(tmp.name, path) + + +class SystemIdentifier(NamedTuple): profile: str | None - generation_number: int + generation: int specialisation: str | None -@dataclass(frozen=True) -class DiskEntry: - entry: Entry - counters: str | None - description: str | None - kernel: Path - initrd: Path - devicetree: Path | None - kernel_params: str | None - machine_id: str | None - sort_key: str +@dataclass +class BootFile: + system_identifier: SystemIdentifier + path: Path + writer: WriteBootFile - @property - def title(self) -> str: - return "{name}{profile}{specialisation}".format( - name=DISTRO_NAME, - profile=" [" + self.entry.profile + "]" if self.entry.profile else "", - specialisation=" (%s)" % self.entry.specialisation - if self.entry.specialisation - else "", + @staticmethod + def from_source(system_identifier: SystemIdentifier, source: Path) -> "BootFile": + return BootFile( + system_identifier=system_identifier, + path=boot_path(source), + writer=CopyWriter(source=source), ) - def serialise(self) -> str: - boot_entry = [ - f"title {self.title}", - f"version {self.description}" if self.description is not None else None, - f"linux /{self.kernel}", - f"initrd /{self.initrd}", - f"options {self.kernel_params}" if self.kernel_params is not None else None, - f"machine-id {self.machine_id}" if self.machine_id is not None else None, - f"devicetree /{self.devicetree}" if self.devicetree is not None else None, - f"sort-key {self.sort_key}", - ] - return "\n".join(filter(None, boot_entry)) - - def write(self) -> GcRoot: - # Check first if the file already exists - for e in os.scandir(path=BOOT_MOUNT_POINT / "loader" / "entries"): - match = re.fullmatch( - rf"{self.path_prefix}(\+[0-9]+(-[0-9]+)?)?\.conf", e.name + @staticmethod + def from_initrd( + system_identifier: SystemIdentifier, source: Path, initrd_secrets: Path | None + ) -> "BootFile": + if initrd_secrets is None: + return BootFile.from_source(system_identifier, source) + else: + # We're trying to calculate a canonical path unique to + # this initrd and secret-appender. The boot_path is the + # canonical path for files that don't need modifications, + # so it serves as a perfect proxy for the unique + # information to combine for a combined unique path. The + # original paths themselves would have also been fine, but + # boot_path is more semantically representative, since + # it's the actual path whose uniqueness we're trying to + # ensure for other things. + combined = "\n".join( + [str(boot_path(source)), str(boot_path(initrd_secrets))] + ) + combined_hash = hashlib.sha256(combined.encode("utf-8")).hexdigest() + return BootFile( + system_identifier=system_identifier, + path=NIXOS_DIR / f"{combined_hash}-initrd.efi", + writer=InitrdWithSecretsWriter( + source=source, initrd_secrets=initrd_secrets + ), ) - if match: - # Check that the contents match the hash - with open(e.path, "r") as f: - hash = hashlib.sha256(f.read().encode("utf-8")).hexdigest() - if hash == self.content_hash: - # The contents match, we are done, there is nothing to write - return GcRoot.from_prefix( - BOOT_MOUNT_POINT / "loader" / "entries" / self.path_prefix - ) - # We didn't find a matching file, so we'll create one - tmp_path = self.path.with_suffix(".tmp") - with tmp_path.open("w") as f: - boot_entry = self.serialise() - - f.write(boot_entry) - f.flush() - os.fsync(f.fileno()) - tmp_path.rename(self.path) - return GcRoot.from_prefix( - BOOT_MOUNT_POINT / "loader" / "entries" / self.path_prefix + @staticmethod + def from_entry( + system_identifier: SystemIdentifier, contents: bytes + ) -> tuple["BootFile", str]: + contents_hash = hashlib.sha256(contents).hexdigest() + path_prefix = f"nixos-{contents_hash}" + path = None + for e in os.scandir(path=BOOT_MOUNT_POINT / "loader" / "entries"): + mat = re.fullmatch( + rf"{re.escape(path_prefix)}(\+[0-9]+(-[0-9]+)?)?\.conf", e.name + ) + if mat is not None: + path = Path("loader/entries") / e.name + break + if path is None: + counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else "" + path = Path(f"loader/entries/{path_prefix}{counters}.conf") + return ( + BootFile( + system_identifier=system_identifier, + path=path, + writer=ContentsWriter(contents=contents), + ), + f"{path_prefix}.conf", ) - @property - def content_hash(self) -> str: - return hashlib.sha256(self.serialise().encode("utf-8")).hexdigest() - @property - def path_prefix(self) -> str: - return "-".join( - p - for p in [ - "nixos", - self.content_hash, - ] - if p - ) - - @property - def path(self) -> Path: - return BOOT_MOUNT_POINT / "loader" / "entries" / self.filename - - @property - def filename(self) -> str: - return f"{self.path_prefix}{self.counters if self.counters else ''}.conf" - - @property - def bootctl_id(self) -> str: - return f"{self.path_prefix}.conf" +# This gets its own type alias to document that the order is very +# important. The order ensures that entry files are written after +# their respective kernel / initrd / etc. +type BootFileList = list[BootFile] libc = ctypes.CDLL("libc.so.6") @@ -180,22 +213,6 @@ def run( return subprocess.run(cmd, check=True, text=True, stdout=stdout, stderr=sys.stderr) -class SystemIdentifier(NamedTuple): - profile: str | None - generation: int - specialisation: str | None - - -def copy_if_not_exists(source: Path, dest: Path) -> None: - if not dest.exists(): - tmpfd, tmppath = tempfile.mkstemp( - dir=dest.parent, prefix=dest.name, suffix=".tmp." - ) - shutil.copyfile(source, tmppath) - os.fsync(tmpfd) - shutil.move(tmppath, dest) - - def generation_dir(profile: str | None, generation: int) -> Path: if profile: return Path( @@ -294,84 +311,58 @@ def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec: ) -def copy_from_file(file: Path) -> Path: - """ - Copy a file to the boot filesystem (XBOOTLDR if in use, otherwise ESP), basing the destination filename on the store path that's being copied from. Return the destination path, relative to the boot filesystem mountpoint. - """ +def boot_path(file: Path) -> Path: store_file_path = file.resolve() suffix = store_file_path.name store_subdir = store_file_path.relative_to(STORE_DIR).parts[0] - efi_file_path = NIXOS_DIR / ( + return NIXOS_DIR / ( f"{suffix}.efi" if suffix == store_subdir else f"{store_subdir}-{suffix}.efi" ) - copy_if_not_exists(store_file_path, BOOT_MOUNT_POINT / efi_file_path) - return efi_file_path -def write_entry( +def boot_file( profile: str | None, generation: int, specialisation: str | None, machine_id: str | None, bootspec: BootSpec, - current: bool, -) -> tuple[DiskEntry, set[GcRoot]]: - gc_roots = set() - +) -> tuple[BootFileList, str]: + system_identifier = SystemIdentifier(profile, generation, specialisation) if specialisation: bootspec = bootspec.specialisations[specialisation] - kernel = copy_from_file(bootspec.kernel) - gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / kernel))) - initrd = copy_from_file(bootspec.initrd) - gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / initrd))) - devicetree = ( - copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None + kernel = BootFile.from_source(system_identifier, bootspec.kernel) + initrd = BootFile.from_initrd( + system_identifier, + bootspec.initrd, + Path(bootspec.initrdSecrets) if bootspec.initrdSecrets is not None else None, ) - if devicetree is not None: - gc_roots.add(GcRoot.from_path(path=(BOOT_MOUNT_POINT / devicetree))) + devicetree = None + if bootspec.devicetree is not None: + devicetree = BootFile.from_source(system_identifier, bootspec.devicetree) - kernel_params = "init=%s " % bootspec.init - - kernel_params = kernel_params + " ".join(bootspec.kernelParams) + kernel_params = " ".join([f"init={bootspec.init}"] + bootspec.kernelParams) build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) build_date = datetime.datetime.fromtimestamp(build_time).strftime("%F") - counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else None - entry = Entry(profile, generation, specialisation) - - disk_entry = DiskEntry( - entry=entry, - kernel=kernel, - initrd=initrd, - devicetree=devicetree, - counters=counters, - kernel_params=kernel_params, - machine_id=machine_id, - description=f"Generation {generation} {bootspec.label}, built on {build_date}", - sort_key=bootspec.sortKey, + title = "{name}{profile}{specialisation}".format( + name=DISTRO_NAME, + profile=" [" + profile + "]" if profile else "", + specialisation=" (%s)" % specialisation if specialisation else "", ) - - try: - if bootspec.initrdSecrets is not None: - run([bootspec.initrdSecrets, BOOT_MOUNT_POINT / initrd]) - except subprocess.CalledProcessError: - if current: - print("failed to create initrd secrets!", file=sys.stderr) - sys.exit(1) - else: - print( - "warning: failed to create initrd secrets " - f'for "{disk_entry.title} - Configuration {generation}", an older generation', - file=sys.stderr, - ) - print( - "note: this is normal after having removed " - "or renamed a file in `boot.initrd.secrets`", - file=sys.stderr, - ) - - gc_roots.add(disk_entry.write()) - return disk_entry, gc_roots + description = f"Generation {generation} {bootspec.label}, built on {build_date}" + boot_entry = [ + f"title {title}", + f"version {description}", + f"linux /{str(kernel.path)}", + f"initrd /{str(initrd.path)}", + f"options {kernel_params}", + f"machine-id {machine_id}" if machine_id is not None else None, + f"devicetree /{str(devicetree.path)}" if devicetree is not None else None, + f"sort-key {bootspec.sortKey}", + ] + contents = "\n".join(filter(None, boot_entry)) + entry, bootctl_id = BootFile.from_entry(system_identifier, contents.encode("utf-8")) + return (list(filter(None, [kernel, initrd, devicetree, entry])), bootctl_id) def get_generations(profile: str | None = None) -> list[SystemIdentifier]: @@ -511,65 +502,49 @@ def install_bootloader(args: argparse.Namespace) -> None: for profile in get_profiles(): gens += get_generations(profile) - gc_roots: set[GcRoot] = set() + boot_files: BootFileList = [] default_config = Path(args.default_config) default_entry_id: str | None = None for gen in gens: - try: - bootspec = get_bootspec(gen.profile, gen.generation) - is_default = Path(bootspec.init).parent == default_config - disk_entry, new_gc_roots = write_entry( - *gen, machine_id, bootspec, current=is_default + bootspec = get_bootspec(gen.profile, gen.generation) + is_default = Path(bootspec.init).parent == default_config + new_boot_files, new_bootctl_id = boot_file(*gen, machine_id, bootspec) + boot_files.extend(new_boot_files) + if is_default: + default_entry_id = new_bootctl_id + for specialisation_name, specialisation in bootspec.specialisations.items(): + is_default = Path(specialisation.init).parent == default_config + new_boot_files, new_bootctl_id = boot_file( + gen.profile, + gen.generation, + specialisation_name, + machine_id, + bootspec, ) - gc_roots.update(new_gc_roots) + boot_files.extend(new_boot_files) if is_default: - default_entry_id = disk_entry.bootctl_id - for specialisation_name, specialisation in bootspec.specialisations.items(): - is_default = Path(specialisation.init).parent == default_config - disk_entry, new_gc_roots = write_entry( - gen.profile, - gen.generation, - specialisation_name, - machine_id, - bootspec, - current=is_default, - ) - gc_roots.update(new_gc_roots) - if is_default: - default_entry_id = disk_entry.bootctl_id - except OSError as e: - # See https://github.com/NixOS/nixpkgs/issues/114552 - if e.errno == errno.EINVAL: - profile = ( - f"profile '{gen.profile}'" if gen.profile else "default profile" - ) - print( - "ignoring {} in the list of boot entries because of the following error:\n{}".format( - profile, e - ), - file=sys.stderr, - ) - else: - raise e + default_entry_id = new_bootctl_id write_loader_conf(default_entry_id) + # Garbage-collect stale kernels/initrds/entries before re-populating extra + # files, so that user-supplied extraEntries (which may also live under + # loader/entries and start with `nixos-`) are not removed again. + garbage_collect(boot_files) + + write_boot_files(boot_files) + + remove_extra_files() + run([COPY_EXTRA_FILES]) + if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: # Cleanup any entries in ESP if xbootldrMountPoint is set. # If the user later unsets xbootldrMountPoint, entries in XBOOTLDR will not be cleaned up # automatically, as we don't have information about the mount point anymore. cleanup_esp() - # Garbage-collect stale kernels/initrds/entries before re-populating extra - # files, so that user-supplied extraEntries (which may also live under - # loader/entries and start with `nixos-`) are not removed again. - garbage_collect(gc_roots) - - remove_extra_files() - run([COPY_EXTRA_FILES]) - def remove_extra_files() -> None: extra_files_dir = BOOT_MOUNT_POINT / NIXOS_DIR / ".extra-files" @@ -589,16 +564,12 @@ def remove_extra_files() -> None: extra_files_dir.mkdir(parents=True, exist_ok=True) -def garbage_collect(gc_roots: set[GcRoot]) -> None: +def garbage_collect(gc_roots: BootFileList) -> None: # Check if a file is in the list of gc roots. - # For prefixes, we need to allow for the potential presence of boot counters. def has_gc_root(p: Path) -> bool: - for root in gc_roots: - if root.path and root.path == p: - return True - elif root.prefix and re.fullmatch( - rf"{re.escape(str(root.prefix))}(\+[0-9]+(-[0-9]+)?)?\.conf", str(p) - ): + for gc_root in gc_roots: + gc_root_path = BOOT_MOUNT_POINT / gc_root.path + if gc_root_path == p: return True return False @@ -615,6 +586,13 @@ def garbage_collect(gc_roots: set[GcRoot]) -> None: delete_path(e) +def write_boot_files(boot_files: BootFileList) -> None: + for boot_file in boot_files: + boot_path = BOOT_MOUNT_POINT / boot_file.path + if not boot_path.exists(): + boot_file.writer.write_boot_file(boot_path) + + def main() -> None: parser = argparse.ArgumentParser( description=f"Update {DISTRO_NAME}-related systemd-boot files" From 44a974d0ebfd4ff8960cdac36480663a5fe13219 Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Fri, 1 May 2026 13:06:02 -0400 Subject: [PATCH 34/55] nixos/systemd-boot: Rerun secrets every switch --- .../systemd-boot/systemd-boot-builder.py | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 88c1bac112fa..b8eeaf7eb393 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -65,6 +65,8 @@ class CopyWriter: source: Path def write_boot_file(self, path: Path) -> None: + if path.exists(): + return with tempfile.NamedTemporaryFile( mode="wb", dir=path.parent, @@ -86,20 +88,29 @@ class InitrdWithSecretsWriter: initrd_secrets: Path def write_boot_file(self, path: Path) -> None: - with tempfile.NamedTemporaryFile( - mode="wb", - dir=path.parent, - delete=False, - prefix=path.name, - suffix=".tmp", - ) as tmp: - with open(self.source, mode="rb") as source_file: - shutil.copyfileobj(source_file, tmp) - tmp.flush() - run([self.initrd_secrets, tmp.name]) - os.fsync(tmp.fileno()) - tmp.close() - os.rename(tmp.name, path) + if path.exists(): + # TODO: This is for matching old behavior. Previously, we + # appended secrets to initrd every time no matter what, so + # we must continue doing that. One potential alternative + # would be running the secret script on an empty file and + # using the hashed contents of that file as part of the + # key that the path name is based on. + run([self.initrd_secrets, path]) + else: + with tempfile.NamedTemporaryFile( + mode="wb", + dir=path.parent, + delete=False, + prefix=path.name, + suffix=".tmp", + ) as tmp: + with open(self.source, mode="rb") as source_file: + shutil.copyfileobj(source_file, tmp) + tmp.flush() + run([self.initrd_secrets, tmp.name]) + os.fsync(tmp.fileno()) + tmp.close() + os.rename(tmp.name, path) @dataclass @@ -107,6 +118,8 @@ class ContentsWriter: contents: bytes def write_boot_file(self, path: Path) -> None: + if path.exists(): + return with tempfile.NamedTemporaryFile( mode="wb", dir=path.parent, @@ -589,8 +602,7 @@ def garbage_collect(gc_roots: BootFileList) -> None: def write_boot_files(boot_files: BootFileList) -> None: for boot_file in boot_files: boot_path = BOOT_MOUNT_POINT / boot_file.path - if not boot_path.exists(): - boot_file.writer.write_boot_file(boot_path) + boot_file.writer.write_boot_file(boot_path) def main() -> None: From 6ef460ec9d4499a8214a45dc497fd4356e5702c2 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:16:18 +0200 Subject: [PATCH 35/55] nixos/systemd-boot-builder: write loader.conf after the entries it points at A crash between the two would leave `default ` referring to a .conf that does not exist yet. --- .../system/boot/loader/systemd-boot/systemd-boot-builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index b8eeaf7eb393..b22d12cf28eb 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -540,8 +540,6 @@ def install_bootloader(args: argparse.Namespace) -> None: if is_default: default_entry_id = new_bootctl_id - write_loader_conf(default_entry_id) - # Garbage-collect stale kernels/initrds/entries before re-populating extra # files, so that user-supplied extraEntries (which may also live under # loader/entries and start with `nixos-`) are not removed again. @@ -549,6 +547,8 @@ def install_bootloader(args: argparse.Namespace) -> None: write_boot_files(boot_files) + write_loader_conf(default_entry_id) + remove_extra_files() run([COPY_EXTRA_FILES]) From 6eba7d60f3120bfcac0c4be3cd67e26dd475aa09 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:16:42 +0200 Subject: [PATCH 36/55] nixos/systemd-boot-builder: rebuild secret-bearing initrds atomically each run Appending to the existing file made it grow on every rebuild and a failed script could leave it half-written. Always rebuild from the pristine initrd into a temp file and rename into place. --- .../systemd-boot/systemd-boot-builder.py | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index b22d12cf28eb..d3dd29147651 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -88,29 +88,26 @@ class InitrdWithSecretsWriter: initrd_secrets: Path def write_boot_file(self, path: Path) -> None: - if path.exists(): - # TODO: This is for matching old behavior. Previously, we - # appended secrets to initrd every time no matter what, so - # we must continue doing that. One potential alternative - # would be running the secret script on an empty file and - # using the hashed contents of that file as part of the - # key that the path name is based on. - run([self.initrd_secrets, path]) - else: - with tempfile.NamedTemporaryFile( - mode="wb", - dir=path.parent, - delete=False, - prefix=path.name, - suffix=".tmp", - ) as tmp: - with open(self.source, mode="rb") as source_file: - shutil.copyfileobj(source_file, tmp) - tmp.flush() - run([self.initrd_secrets, tmp.name]) - os.fsync(tmp.fileno()) - tmp.close() - os.rename(tmp.name, path) + # Secrets can change between rebuilds, so always rebuild from the + # pristine initrd into a temp file and rename into place. + tmp = tempfile.NamedTemporaryFile( + mode="wb", + dir=path.parent, + delete=False, + prefix=path.name, + suffix=".tmp", + ) + try: + with open(self.source, mode="rb") as source_file: + shutil.copyfileobj(source_file, tmp) + tmp.close() + run([self.initrd_secrets, tmp.name]) + with open(tmp.name, "rb") as f: + os.fsync(f.fileno()) + except BaseException: + os.unlink(tmp.name) + raise + os.rename(tmp.name, path) @dataclass From 146acf965f4940311f4a988aabe0dca7fd353cea Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:17:24 +0200 Subject: [PATCH 37/55] nixos/systemd-boot-builder: warn instead of aborting when an old gen's secrets fail After removing or renaming a file in boot.initrd.secrets, older generations' append scripts start failing. Aborting on that blocks deploying the new configuration, so only treat a failure as fatal when it belongs to the configuration being switched to. --- .../systemd-boot/systemd-boot-builder.py | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index d3dd29147651..6f46d38286c3 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -140,23 +140,30 @@ class SystemIdentifier(NamedTuple): @dataclass class BootFile: system_identifier: SystemIdentifier + current: bool path: Path writer: WriteBootFile @staticmethod - def from_source(system_identifier: SystemIdentifier, source: Path) -> "BootFile": + def from_source( + system_identifier: SystemIdentifier, current: bool, source: Path + ) -> "BootFile": return BootFile( system_identifier=system_identifier, + current=current, path=boot_path(source), writer=CopyWriter(source=source), ) @staticmethod def from_initrd( - system_identifier: SystemIdentifier, source: Path, initrd_secrets: Path | None + system_identifier: SystemIdentifier, + current: bool, + source: Path, + initrd_secrets: Path | None, ) -> "BootFile": if initrd_secrets is None: - return BootFile.from_source(system_identifier, source) + return BootFile.from_source(system_identifier, current, source) else: # We're trying to calculate a canonical path unique to # this initrd and secret-appender. The boot_path is the @@ -173,6 +180,7 @@ class BootFile: combined_hash = hashlib.sha256(combined.encode("utf-8")).hexdigest() return BootFile( system_identifier=system_identifier, + current=current, path=NIXOS_DIR / f"{combined_hash}-initrd.efi", writer=InitrdWithSecretsWriter( source=source, initrd_secrets=initrd_secrets @@ -181,7 +189,7 @@ class BootFile: @staticmethod def from_entry( - system_identifier: SystemIdentifier, contents: bytes + system_identifier: SystemIdentifier, current: bool, contents: bytes ) -> tuple["BootFile", str]: contents_hash = hashlib.sha256(contents).hexdigest() path_prefix = f"nixos-{contents_hash}" @@ -199,6 +207,7 @@ class BootFile: return ( BootFile( system_identifier=system_identifier, + current=current, path=path, writer=ContentsWriter(contents=contents), ), @@ -336,19 +345,23 @@ def boot_file( specialisation: str | None, machine_id: str | None, bootspec: BootSpec, + current: bool, ) -> tuple[BootFileList, str]: system_identifier = SystemIdentifier(profile, generation, specialisation) if specialisation: bootspec = bootspec.specialisations[specialisation] - kernel = BootFile.from_source(system_identifier, bootspec.kernel) + kernel = BootFile.from_source(system_identifier, current, bootspec.kernel) initrd = BootFile.from_initrd( system_identifier, + current, bootspec.initrd, Path(bootspec.initrdSecrets) if bootspec.initrdSecrets is not None else None, ) devicetree = None if bootspec.devicetree is not None: - devicetree = BootFile.from_source(system_identifier, bootspec.devicetree) + devicetree = BootFile.from_source( + system_identifier, current, bootspec.devicetree + ) kernel_params = " ".join([f"init={bootspec.init}"] + bootspec.kernelParams) build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) @@ -371,7 +384,9 @@ def boot_file( f"sort-key {bootspec.sortKey}", ] contents = "\n".join(filter(None, boot_entry)) - entry, bootctl_id = BootFile.from_entry(system_identifier, contents.encode("utf-8")) + entry, bootctl_id = BootFile.from_entry( + system_identifier, current, contents.encode("utf-8") + ) return (list(filter(None, [kernel, initrd, devicetree, entry])), bootctl_id) @@ -520,7 +535,9 @@ def install_bootloader(args: argparse.Namespace) -> None: for gen in gens: bootspec = get_bootspec(gen.profile, gen.generation) is_default = Path(bootspec.init).parent == default_config - new_boot_files, new_bootctl_id = boot_file(*gen, machine_id, bootspec) + new_boot_files, new_bootctl_id = boot_file( + *gen, machine_id, bootspec, current=is_default + ) boot_files.extend(new_boot_files) if is_default: default_entry_id = new_bootctl_id @@ -532,6 +549,7 @@ def install_bootloader(args: argparse.Namespace) -> None: specialisation_name, machine_id, bootspec, + current=is_default, ) boot_files.extend(new_boot_files) if is_default: @@ -599,7 +617,22 @@ def garbage_collect(gc_roots: BootFileList) -> None: def write_boot_files(boot_files: BootFileList) -> None: for boot_file in boot_files: boot_path = BOOT_MOUNT_POINT / boot_file.path - boot_file.writer.write_boot_file(boot_path) + try: + boot_file.writer.write_boot_file(boot_path) + except subprocess.CalledProcessError: + if boot_file.current: + print("failed to create initrd secrets!", file=sys.stderr) + sys.exit(1) + print( + "warning: failed to create initrd secrets for generation " + f"{boot_file.system_identifier.generation}, an older generation", + file=sys.stderr, + ) + print( + "note: this is normal after having removed " + "or renamed a file in `boot.initrd.secrets`", + file=sys.stderr, + ) def main() -> None: From 85d59c4f3dfdd9c61e47a0e7e2ced83fb64f1baa Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:17:39 +0200 Subject: [PATCH 38/55] nixos/systemd-boot-builder: use a set for GC root lookup has_gc_root() iterated the entire BootFileList for every file on the ESP, giving O(files * roots) comparisons. Build the set of kept paths once and use O(1) membership tests instead. --- .../boot/loader/systemd-boot/systemd-boot-builder.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 6f46d38286c3..1d2feb0d3dc9 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -593,16 +593,10 @@ def remove_extra_files() -> None: def garbage_collect(gc_roots: BootFileList) -> None: - # Check if a file is in the list of gc roots. - def has_gc_root(p: Path) -> bool: - for gc_root in gc_roots: - gc_root_path = BOOT_MOUNT_POINT / gc_root.path - if gc_root_path == p: - return True - return False + keep = {BOOT_MOUNT_POINT / gc_root.path for gc_root in gc_roots} def delete_path(e: os.DirEntry) -> None: - if e.is_file(follow_symlinks=True) and not has_gc_root(Path(e.path)): + if e.is_file(follow_symlinks=True) and Path(e.path) not in keep: os.remove(e.path) for e in os.scandir(BOOT_MOUNT_POINT / NIXOS_DIR): From 820d20f8b90c6b1cedfa5980a24d012e4769246f Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:17:51 +0200 Subject: [PATCH 39/55] nixos/systemd-boot-builder: cache boot_path() It calls Path.resolve() and is invoked several times per generation for the same store paths. --- .../system/boot/loader/systemd-boot/systemd-boot-builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 1d2feb0d3dc9..2a0693f4d237 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -3,6 +3,7 @@ import argparse import ctypes import datetime import errno +import functools import hashlib import os import re @@ -330,6 +331,7 @@ def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec: ) +@functools.lru_cache(maxsize=None) def boot_path(file: Path) -> Path: store_file_path = file.resolve() suffix = store_file_path.name From 3ff32972f8193c02cdcfde6fd579ade9706db16c Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 12:18:11 +0200 Subject: [PATCH 40/55] nixos/systemd-boot-builder: verify content of existing entry files A file named nixos-.conf whose content no longer hashes to is corrupt. Skip it so GC removes it and a fresh entry is written. --- .../loader/systemd-boot/systemd-boot-builder.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 2a0693f4d237..8194f771c5bb 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -194,14 +194,17 @@ class BootFile: ) -> tuple["BootFile", str]: contents_hash = hashlib.sha256(contents).hexdigest() path_prefix = f"nixos-{contents_hash}" + pat = re.compile(rf"{re.escape(path_prefix)}(\+[0-9]+(-[0-9]+)?)?\.conf") path = None for e in os.scandir(path=BOOT_MOUNT_POINT / "loader" / "entries"): - mat = re.fullmatch( - rf"{re.escape(path_prefix)}(\+[0-9]+(-[0-9]+)?)?\.conf", e.name - ) - if mat is not None: - path = Path("loader/entries") / e.name - break + if pat.fullmatch(e.name) is None: + continue + # Ignore files whose content does not match the hash in their + # name so GC removes them and a fresh entry is written. + if hashlib.sha256(Path(e.path).read_bytes()).hexdigest() != contents_hash: + continue + path = Path("loader/entries") / e.name + break if path is None: counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else "" path = Path(f"loader/entries/{path_prefix}{counters}.conf") From 76673e2736395156676302ae3d2293f679ae9bb8 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 13:52:24 +0200 Subject: [PATCH 41/55] nixos/systemd-boot-builder: fall back to pristine initrd when secrets fail Otherwise the .conf for that generation references a missing initrd and the boot entry fails to load. --- .../system/boot/loader/systemd-boot/systemd-boot-builder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 8194f771c5bb..35799e1c458d 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -622,6 +622,10 @@ def write_boot_files(boot_files: BootFileList) -> None: if boot_file.current: print("failed to create initrd secrets!", file=sys.stderr) sys.exit(1) + # Keep the entry bootable by leaving at least a pristine initrd + # in place. CopyWriter is a no-op if one already exists. + assert isinstance(boot_file.writer, InitrdWithSecretsWriter) + CopyWriter(source=boot_file.writer.source).write_boot_file(boot_path) print( "warning: failed to create initrd secrets for generation " f"{boot_file.system_identifier.generation}, an older generation", From b4e756627d341b042f467d725c1f9b0f2d53fbb6 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 4 May 2026 13:52:52 +0200 Subject: [PATCH 42/55] nixos/systemd-boot-builder: write each ESP path only once Shared kernels and initrds appear once per generation in boot_files, so InitrdWithSecretsWriter rebuilt the same file repeatedly. Prefer the current configuration's entry so its failures stay fatal. --- .../boot/loader/systemd-boot/systemd-boot-builder.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 35799e1c458d..3ca697bfe2ea 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -614,7 +614,14 @@ def garbage_collect(gc_roots: BootFileList) -> None: def write_boot_files(boot_files: BootFileList) -> None: - for boot_file in boot_files: + # Deduplicate by destination path so shared files are written once. + # Prefer the current configuration's entry so its failures are fatal. + unique: dict[Path, BootFile] = {} + for bf in boot_files: + if bf.path not in unique or bf.current: + unique[bf.path] = bf + + for boot_file in unique.values(): boot_path = BOOT_MOUNT_POINT / boot_file.path try: boot_file.writer.write_boot_file(boot_path) From 30552ab00b033225943fe34ff8b22050b9110a41 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Sun, 31 May 2026 21:37:41 +0300 Subject: [PATCH 43/55] nixos/systemd-boot-builder: clarify stale initrd secrets warning Tell the user what actually happens (the old secrets stay in place) and how to get rid of the warning, instead of just saying it is "normal". Suggested-by: Will Fancher --- .../boot/loader/systemd-boot/systemd-boot-builder.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 3ca697bfe2ea..1f2210133532 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -634,13 +634,11 @@ def write_boot_files(boot_files: BootFileList) -> None: assert isinstance(boot_file.writer, InitrdWithSecretsWriter) CopyWriter(source=boot_file.writer.source).write_boot_file(boot_path) print( - "warning: failed to create initrd secrets for generation " - f"{boot_file.system_identifier.generation}, an older generation", - file=sys.stderr, - ) - print( - "note: this is normal after having removed " - "or renamed a file in `boot.initrd.secrets`", + "warning: failed to update initrd secrets for an older " + f"generation ({boot_file.system_identifier.generation}). The " + "previous secrets in this initrd will continue to be used. " + "To silence this warning, restore the secret files to their " + "original locations or delete this generation.", file=sys.stderr, ) From dff3315facda625a85013c1debc483959fd24ecc Mon Sep 17 00:00:00 2001 From: r-vdp Date: Sun, 31 May 2026 21:43:02 +0300 Subject: [PATCH 44/55] nixos/systemd-boot-builder: use `with` for the secrets temp file This guarantees the descriptor is closed even when copyfileobj raises, matching the other writer implementations. The append-initrd-secrets script reopens the file by path, so flush() is enough before invoking it and the explicit close() is no longer needed. --- .../systemd-boot/systemd-boot-builder.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 1f2210133532..61791019338d 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -91,24 +91,23 @@ class InitrdWithSecretsWriter: def write_boot_file(self, path: Path) -> None: # Secrets can change between rebuilds, so always rebuild from the # pristine initrd into a temp file and rename into place. - tmp = tempfile.NamedTemporaryFile( + with tempfile.NamedTemporaryFile( mode="wb", dir=path.parent, delete=False, prefix=path.name, suffix=".tmp", - ) - try: - with open(self.source, mode="rb") as source_file: - shutil.copyfileobj(source_file, tmp) - tmp.close() - run([self.initrd_secrets, tmp.name]) - with open(tmp.name, "rb") as f: - os.fsync(f.fileno()) - except BaseException: - os.unlink(tmp.name) - raise - os.rename(tmp.name, path) + ) as tmp: + try: + with open(self.source, mode="rb") as source_file: + shutil.copyfileobj(source_file, tmp) + tmp.flush() + run([self.initrd_secrets, tmp.name]) + os.fsync(tmp.fileno()) + except BaseException: + os.unlink(tmp.name) + raise + os.rename(tmp.name, path) @dataclass From 9d46e91c49a7c161e3fab4002caba67a9571d7bf Mon Sep 17 00:00:00 2001 From: r-vdp Date: Sun, 31 May 2026 22:40:50 +0300 Subject: [PATCH 45/55] nixos/systemd-boot-builder: track critical paths separately from BootFile Whether a write failure must be fatal is a property of the destination path (is it needed by the configuration we are switching to?), not of the particular BootFile instance that happened to survive deduplication. Compute the set of critical paths up front and look it up in write_boot_files, so the dedup loop no longer needs to pick the "right" instance and becomes a plain order-preserving seen-set walk. This leaves BootFile.current unused. Suggested-by: Will Fancher --- .../systemd-boot/systemd-boot-builder.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 61791019338d..f223caac9984 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -532,6 +532,7 @@ def install_bootloader(args: argparse.Namespace) -> None: gens += get_generations(profile) boot_files: BootFileList = [] + critical_paths: set[Path] = set() default_config = Path(args.default_config) default_entry_id: str | None = None @@ -545,6 +546,7 @@ def install_bootloader(args: argparse.Namespace) -> None: boot_files.extend(new_boot_files) if is_default: default_entry_id = new_bootctl_id + critical_paths.update(bf.path for bf in new_boot_files) for specialisation_name, specialisation in bootspec.specialisations.items(): is_default = Path(specialisation.init).parent == default_config new_boot_files, new_bootctl_id = boot_file( @@ -558,13 +560,14 @@ def install_bootloader(args: argparse.Namespace) -> None: boot_files.extend(new_boot_files) if is_default: default_entry_id = new_bootctl_id + critical_paths.update(bf.path for bf in new_boot_files) # Garbage-collect stale kernels/initrds/entries before re-populating extra # files, so that user-supplied extraEntries (which may also live under # loader/entries and start with `nixos-`) are not removed again. garbage_collect(boot_files) - write_boot_files(boot_files) + write_boot_files(boot_files, critical_paths) write_loader_conf(default_entry_id) @@ -612,20 +615,18 @@ def garbage_collect(gc_roots: BootFileList) -> None: delete_path(e) -def write_boot_files(boot_files: BootFileList) -> None: +def write_boot_files(boot_files: BootFileList, critical_paths: set[Path]) -> None: # Deduplicate by destination path so shared files are written once. - # Prefer the current configuration's entry so its failures are fatal. - unique: dict[Path, BootFile] = {} - for bf in boot_files: - if bf.path not in unique or bf.current: - unique[bf.path] = bf - - for boot_file in unique.values(): + seen: set[Path] = set() + for boot_file in boot_files: + if boot_file.path in seen: + continue + seen.add(boot_file.path) boot_path = BOOT_MOUNT_POINT / boot_file.path try: boot_file.writer.write_boot_file(boot_path) except subprocess.CalledProcessError: - if boot_file.current: + if boot_file.path in critical_paths: print("failed to create initrd secrets!", file=sys.stderr) sys.exit(1) # Keep the entry bootable by leaving at least a pristine initrd From c38ca6ab7d3c8b08bbf00fb2b1c8a6fe3bafcee6 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Sun, 31 May 2026 22:55:14 +0300 Subject: [PATCH 46/55] nixos/systemd-boot-builder: handle initrd-secrets failure in the writer The CalledProcessError can only come from the append-initrd-secrets script, so catching it in the generic write loop and then asserting on the writer type to reach back into its `source` is the wrong layer. Move the catch, the pristine-initrd fallback and the warning into InitrdWithSecretsWriter itself, and pass `critical` through the writer protocol so it can decide between aborting and falling back. The writer carries the generation number so the warning can still name the affected generation. write_boot_files no longer knows anything about secrets and the isinstance assertion is gone. Suggested-by: Will Fancher --- .../systemd-boot/systemd-boot-builder.py | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index f223caac9984..84317ad07e7b 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -58,14 +58,14 @@ class BootSpec: class WriteBootFile(Protocol): - def write_boot_file(self, path: Path) -> None: ... + def write_boot_file(self, path: Path, *, critical: bool) -> None: ... @dataclass class CopyWriter: source: Path - def write_boot_file(self, path: Path) -> None: + def write_boot_file(self, path: Path, *, critical: bool) -> None: if path.exists(): return with tempfile.NamedTemporaryFile( @@ -87,8 +87,9 @@ class CopyWriter: class InitrdWithSecretsWriter: source: Path initrd_secrets: Path + generation: int - def write_boot_file(self, path: Path) -> None: + def write_boot_file(self, path: Path, *, critical: bool) -> None: # Secrets can change between rebuilds, so always rebuild from the # pristine initrd into a temp file and rename into place. with tempfile.NamedTemporaryFile( @@ -104,6 +105,24 @@ class InitrdWithSecretsWriter: tmp.flush() run([self.initrd_secrets, tmp.name]) os.fsync(tmp.fileno()) + except subprocess.CalledProcessError: + os.unlink(tmp.name) + if critical: + print("failed to create initrd secrets!", file=sys.stderr) + sys.exit(1) + # Keep the entry bootable by leaving at least a pristine + # initrd in place. CopyWriter is a no-op if one already + # exists. + CopyWriter(source=self.source).write_boot_file(path, critical=False) + print( + "warning: failed to update initrd secrets for an older " + f"generation ({self.generation}). The previous secrets " + "in this initrd will continue to be used. To silence " + "this warning, restore the secret files to their " + "original locations or delete this generation.", + file=sys.stderr, + ) + return except BaseException: os.unlink(tmp.name) raise @@ -114,7 +133,7 @@ class InitrdWithSecretsWriter: class ContentsWriter: contents: bytes - def write_boot_file(self, path: Path) -> None: + def write_boot_file(self, path: Path, *, critical: bool) -> None: if path.exists(): return with tempfile.NamedTemporaryFile( @@ -183,7 +202,9 @@ class BootFile: current=current, path=NIXOS_DIR / f"{combined_hash}-initrd.efi", writer=InitrdWithSecretsWriter( - source=source, initrd_secrets=initrd_secrets + source=source, + initrd_secrets=initrd_secrets, + generation=system_identifier.generation, ), ) @@ -622,25 +643,10 @@ def write_boot_files(boot_files: BootFileList, critical_paths: set[Path]) -> Non if boot_file.path in seen: continue seen.add(boot_file.path) - boot_path = BOOT_MOUNT_POINT / boot_file.path - try: - boot_file.writer.write_boot_file(boot_path) - except subprocess.CalledProcessError: - if boot_file.path in critical_paths: - print("failed to create initrd secrets!", file=sys.stderr) - sys.exit(1) - # Keep the entry bootable by leaving at least a pristine initrd - # in place. CopyWriter is a no-op if one already exists. - assert isinstance(boot_file.writer, InitrdWithSecretsWriter) - CopyWriter(source=boot_file.writer.source).write_boot_file(boot_path) - print( - "warning: failed to update initrd secrets for an older " - f"generation ({boot_file.system_identifier.generation}). The " - "previous secrets in this initrd will continue to be used. " - "To silence this warning, restore the secret files to their " - "original locations or delete this generation.", - file=sys.stderr, - ) + boot_file.writer.write_boot_file( + BOOT_MOUNT_POINT / boot_file.path, + critical=boot_file.path in critical_paths, + ) def main() -> None: From 9eb570f4531938ef689c3114892ca17ff0ffd185 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Sun, 31 May 2026 23:10:20 +0300 Subject: [PATCH 47/55] nixos/systemd-boot-builder: drop unused BootFile.{current,system_identifier} Both fields are now write-only after the previous two commits, so remove them. BootFile is back to being just a (path, writer) pair. --- .../systemd-boot/systemd-boot-builder.py | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 84317ad07e7b..801b598233ae 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -158,31 +158,24 @@ class SystemIdentifier(NamedTuple): @dataclass class BootFile: - system_identifier: SystemIdentifier - current: bool path: Path writer: WriteBootFile @staticmethod - def from_source( - system_identifier: SystemIdentifier, current: bool, source: Path - ) -> "BootFile": + def from_source(source: Path) -> "BootFile": return BootFile( - system_identifier=system_identifier, - current=current, path=boot_path(source), writer=CopyWriter(source=source), ) @staticmethod def from_initrd( - system_identifier: SystemIdentifier, - current: bool, + generation: int, source: Path, initrd_secrets: Path | None, ) -> "BootFile": if initrd_secrets is None: - return BootFile.from_source(system_identifier, current, source) + return BootFile.from_source(source) else: # We're trying to calculate a canonical path unique to # this initrd and secret-appender. The boot_path is the @@ -198,20 +191,16 @@ class BootFile: ) combined_hash = hashlib.sha256(combined.encode("utf-8")).hexdigest() return BootFile( - system_identifier=system_identifier, - current=current, path=NIXOS_DIR / f"{combined_hash}-initrd.efi", writer=InitrdWithSecretsWriter( source=source, initrd_secrets=initrd_secrets, - generation=system_identifier.generation, + generation=generation, ), ) @staticmethod - def from_entry( - system_identifier: SystemIdentifier, current: bool, contents: bytes - ) -> tuple["BootFile", str]: + def from_entry(contents: bytes) -> tuple["BootFile", str]: contents_hash = hashlib.sha256(contents).hexdigest() path_prefix = f"nixos-{contents_hash}" pat = re.compile(rf"{re.escape(path_prefix)}(\+[0-9]+(-[0-9]+)?)?\.conf") @@ -230,8 +219,6 @@ class BootFile: path = Path(f"loader/entries/{path_prefix}{counters}.conf") return ( BootFile( - system_identifier=system_identifier, - current=current, path=path, writer=ContentsWriter(contents=contents), ), @@ -370,23 +357,18 @@ def boot_file( specialisation: str | None, machine_id: str | None, bootspec: BootSpec, - current: bool, ) -> tuple[BootFileList, str]: - system_identifier = SystemIdentifier(profile, generation, specialisation) if specialisation: bootspec = bootspec.specialisations[specialisation] - kernel = BootFile.from_source(system_identifier, current, bootspec.kernel) + kernel = BootFile.from_source(bootspec.kernel) initrd = BootFile.from_initrd( - system_identifier, - current, + generation, bootspec.initrd, Path(bootspec.initrdSecrets) if bootspec.initrdSecrets is not None else None, ) devicetree = None if bootspec.devicetree is not None: - devicetree = BootFile.from_source( - system_identifier, current, bootspec.devicetree - ) + devicetree = BootFile.from_source(bootspec.devicetree) kernel_params = " ".join([f"init={bootspec.init}"] + bootspec.kernelParams) build_time = int(system_dir(profile, generation, specialisation).stat().st_ctime) @@ -409,9 +391,7 @@ def boot_file( f"sort-key {bootspec.sortKey}", ] contents = "\n".join(filter(None, boot_entry)) - entry, bootctl_id = BootFile.from_entry( - system_identifier, current, contents.encode("utf-8") - ) + entry, bootctl_id = BootFile.from_entry(contents.encode("utf-8")) return (list(filter(None, [kernel, initrd, devicetree, entry])), bootctl_id) @@ -561,9 +541,7 @@ def install_bootloader(args: argparse.Namespace) -> None: for gen in gens: bootspec = get_bootspec(gen.profile, gen.generation) is_default = Path(bootspec.init).parent == default_config - new_boot_files, new_bootctl_id = boot_file( - *gen, machine_id, bootspec, current=is_default - ) + new_boot_files, new_bootctl_id = boot_file(*gen, machine_id, bootspec) boot_files.extend(new_boot_files) if is_default: default_entry_id = new_bootctl_id @@ -576,7 +554,6 @@ def install_bootloader(args: argparse.Namespace) -> None: specialisation_name, machine_id, bootspec, - current=is_default, ) boot_files.extend(new_boot_files) if is_default: From deed68a0adcf2e7b959fe0805721e0d84ea69f3f Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 09:30:06 +0000 Subject: [PATCH 48/55] roon-server: 2.66.1658 -> 2.67.1661 --- pkgs/by-name/ro/roon-server/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/ro/roon-server/package.nix b/pkgs/by-name/ro/roon-server/package.nix index a5b8f1713db8..cf8da75d7d3e 100644 --- a/pkgs/by-name/ro/roon-server/package.nix +++ b/pkgs/by-name/ro/roon-server/package.nix @@ -16,7 +16,7 @@ stdenv, }: let - version = "2.66.1658"; + version = "2.67.1661"; urlVersion = builtins.replaceStrings [ "." ] [ "0" ] version; in stdenv.mkDerivation { @@ -25,7 +25,7 @@ stdenv.mkDerivation { src = fetchurl { url = "https://download.roonlabs.com/updates/production/RoonServer_linuxx64_${urlVersion}.tar.bz2"; - hash = "sha256-IY0DiTiq7n7kv3AYt3JuyFUXZXAzUpO/4yJoCvA9Hdk="; + hash = "sha256-5IvAJCTcWaIHTkWZYT7zPTPSjvLuQigF4BHH4l0wTR0="; }; dontConfigure = true; From 87987928a056812b422f6c4feda5d5b5aa99553e Mon Sep 17 00:00:00 2001 From: ekzyis Date: Tue, 2 Jun 2026 12:02:17 +0200 Subject: [PATCH 49/55] aflplusplus: 4.35c -> 4.40c --- .../security/aflplusplus/aflpp-v4.40c.patch | 14 +++++++++++++ pkgs/tools/security/aflplusplus/default.nix | 20 ++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 pkgs/tools/security/aflplusplus/aflpp-v4.40c.patch diff --git a/pkgs/tools/security/aflplusplus/aflpp-v4.40c.patch b/pkgs/tools/security/aflplusplus/aflpp-v4.40c.patch new file mode 100644 index 000000000000..c845e514db95 --- /dev/null +++ b/pkgs/tools/security/aflplusplus/aflpp-v4.40c.patch @@ -0,0 +1,14 @@ +diff --git a/test/test-all.sh b/test/test-all.sh +index 2a47b21e..a45ca580 100755 +--- a/test/test-all.sh ++++ b/test/test-all.sh +@@ -15,7 +15,8 @@ for script in test-*.sh; do + # Skip exclusions + if [ "$script" = "test-pre.sh" ] || \ + [ "$script" = "test-post.sh" ] || \ +- [ "$script" = "test-all.sh" ]; then ++ [ "$script" = "test-all.sh" ] || \ ++ [ "$script" = "test-performance.sh" ]; then + continue + fi + diff --git a/pkgs/tools/security/aflplusplus/default.nix b/pkgs/tools/security/aflplusplus/default.nix index 9e92be083985..924944735cb6 100644 --- a/pkgs/tools/security/aflplusplus/default.nix +++ b/pkgs/tools/security/aflplusplus/default.nix @@ -53,7 +53,7 @@ let aflplusplus = stdenvNoCC.mkDerivation rec { pname = "aflplusplus"; - version = "4.35c"; + version = "4.40c"; src = fetchFromGitHub { owner = "AFLplusplus"; @@ -61,9 +61,9 @@ let tag = "v${version}"; hash = if withNyx then - "sha256-srHrYPEb0UAP/G9cOxJOZ9D6v9pxqez28suPsa70E2M=" + "sha256-901rJfuMZvgUpQ6zzboa7lu9yhSyX+0u+HUk8oGsqgo=" else - "sha256-j5YH39JKcjYuDqyl+KRMtgn3UoeWEW1z7m4ysf2uilc="; + "sha256-QtGazGShjybvjOONoWjqSg/c+l5sPpaFuuTI2S85YQM="; fetchSubmodules = withNyx; }; @@ -89,10 +89,16 @@ let # warning: "_FORTIFY_SOURCE" redefined hardeningDisable = [ "fortify" ]; - # We build nyx mode dependencies ourselves, so this patch skips - # build_nyx_support.sh in the aflplusplus source code. It also skips - # test-nyx-mode.sh because we can't test nyx mode in the sandbox. - patches = lib.optional withNyx ./nyx_mode/nyx_mode.patch; + patches = [ + # skip performance test: it's skipped anyway, but exits with code 1 + ./aflpp-v4.40c.patch + ] + ++ lib.optionals withNyx [ + # We build nyx mode dependencies ourselves, so this patch skips + # build_nyx_support.sh in the aflplusplus source code. It also skips + # test-nyx-mode.sh because we can't test nyx mode in the sandbox. + ./nyx_mode/nyx_mode.patch + ]; postPatch = '' # Don't care about this. rm Android.bp From 1c437aa25f0b5ebce94049e4bf8a5e3f7da67a2e Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 10:25:15 +0000 Subject: [PATCH 50/55] trufflehog: 3.95.3 -> 3.95.4 --- pkgs/by-name/tr/trufflehog/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/tr/trufflehog/package.nix b/pkgs/by-name/tr/trufflehog/package.nix index 310fbcf9507d..f965c8a23e4a 100644 --- a/pkgs/by-name/tr/trufflehog/package.nix +++ b/pkgs/by-name/tr/trufflehog/package.nix @@ -8,16 +8,16 @@ buildGoModule (finalAttrs: { pname = "trufflehog"; - version = "3.95.3"; + version = "3.95.4"; src = fetchFromGitHub { owner = "trufflesecurity"; repo = "trufflehog"; tag = "v${finalAttrs.version}"; - hash = "sha256-FFYDlETo7r2j1e4l+oJ3yWn1hUyA/4Xe7onrX8GidZg="; + hash = "sha256-6n3gVr6+P5hWLFUAWByIYtlUkmpOmBF9Ffy5GC8awT0="; }; - vendorHash = "sha256-2WBdBsOXjj4/9hLA+yk5PQAqOgi5vn1cH4NnkHg8umI="; + vendorHash = "sha256-zA5CYHNhpbRhFrnjDNUV30sw+qXAJupMl7uvgOu62lU="; nativeBuildInputs = [ makeWrapper ]; From 2edfc5c231d4db9e199127a862958cf9e792284e Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 2 Jun 2026 12:47:18 +0200 Subject: [PATCH 51/55] silver-searcher: fix package name in throw --- pkgs/top-level/aliases.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 192c8db0d5ae..fdae54306e9f 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -1959,7 +1959,7 @@ mapAliases { sierra-breeze-enhanced = throw "'sierra-breeze-enhanced' has been removed, as it is only compatible with Plasma 5, which is EOL"; # Added 2025-08-20 signal-desktop-bin = throw "'signal-desktop-bin' has been replaced by 'signal-desktop' which is built from source"; # Added 2026-03-02 signal-desktop-source = throw "'signal-desktop-source' has been renamed to/replaced by 'signal-desktop'"; # Converted to throw 2025-10-27 - silver-searcher = throw "'silver-surfer' has been removed as it has seen no development since 2020 and is stuck on the obsolete pcre library"; + silver-searcher = throw "'silver-searcher' has been removed as it has seen no development since 2020 and is stuck on the obsolete pcre library"; simpleBluez = warnAlias "'simpleBluez' has been renamed to 'simplebluez'" simplebluez; # Added 2026-02-18 simpleDBus = warnAlias "'simpleDBus' has been renamed to 'simpledbus'" simpledbus; # Added 2026-02-12 simplesamlphp = throw "'simplesamlphp' was removed because it was unmaintained in nixpkgs"; # Added 2025-10-17 From 2b2b69cc17df9ea22c9e9c36b2655c854068855c Mon Sep 17 00:00:00 2001 From: winston Date: Mon, 25 May 2026 12:30:39 +0200 Subject: [PATCH 52/55] nixos/gdm: ensure environment from display-manager.service is propagated --- nixos/modules/services/display-managers/gdm.nix | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nixos/modules/services/display-managers/gdm.nix b/nixos/modules/services/display-managers/gdm.nix index dd043cbf272a..b10c60436919 100644 --- a/nixos/modules/services/display-managers/gdm.nix +++ b/nixos/modules/services/display-managers/gdm.nix @@ -455,6 +455,22 @@ in settings.conffile = "/etc/pam/environment"; settings.readenv = 0; } + # make sure the spawned session has the same variables as `display-manager.service` + # https://github.com/NixOS/nixpkgs/issues/523332 + { + name = "env-greeter"; + control = "required"; + modulePath = "${config.security.pam.package}/lib/security/pam_env.so"; + settings.conffile = + let + env = config.services.displayManager.generic.environment; + in + pkgs.writeText "gdm-launch-environment-env-conf" '' + PATH DEFAULT="''${PATH}:${pkgs.gnome-session}/bin" + XDG_DATA_DIRS DEFAULT="''${XDG_DATA_DIRS}:${env.XDG_DATA_DIRS}" + ''; + settings.readenv = 0; + } { name = "systemd"; control = "optional"; From a6548b331ca524f8bf2fb13448bbfafbea7efb7d Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 11:19:34 +0000 Subject: [PATCH 53/55] radicle-ci-broker: 0.28.0 -> 0.28.1 --- pkgs/by-name/ra/radicle-ci-broker/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ra/radicle-ci-broker/package.nix b/pkgs/by-name/ra/radicle-ci-broker/package.nix index cbe7734b2ca9..432601db7dc9 100644 --- a/pkgs/by-name/ra/radicle-ci-broker/package.nix +++ b/pkgs/by-name/ra/radicle-ci-broker/package.nix @@ -14,14 +14,14 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "radicle-ci-broker"; - version = "0.28.0"; + version = "0.28.1"; src = fetchFromRadicle { seed = "seed.radicle.dev"; repo = "zwTxygwuz5LDGBq255RA2CbNGrz8"; node = "z6MkgEMYod7Hxfy9qCvDv5hYHkZ4ciWmLFgfvm3Wn1b2w2FV"; tag = "v${finalAttrs.version}"; - hash = "sha256-p+fOCS9Z5x8pwwgtz08wj6noY1CIGkeqQ4TKgPeBPWA="; + hash = "sha256-6jCMphwVhRgtLpUWBLwsODgR41wl27hyzbMdDjMISfM="; leaveDotGit = true; postFetch = '' git -C $out rev-parse --short HEAD > $out/.git_head @@ -29,7 +29,7 @@ rustPlatform.buildRustPackage (finalAttrs: { ''; }; - cargoHash = "sha256-J0tUgtK1mj/su/IxKDdWXoWpWZBOUHLr4n9gbLWc8bU="; + cargoHash = "sha256-fZwvjpTWWbHDSSI1tXF1JhyvZnIfP4p601GoVBJwR8k="; postPatch = '' substituteInPlace build.rs \ From 9286b38f74cee18c663596ac565451bc8c3f04ac Mon Sep 17 00:00:00 2001 From: "R. Ryantm" Date: Tue, 2 Jun 2026 11:51:22 +0000 Subject: [PATCH 54/55] home-assistant-custom-components.waste_collection_schedule: 2.25.0 -> 2.26.0 --- .../custom-components/waste_collection_schedule/package.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/servers/home-assistant/custom-components/waste_collection_schedule/package.nix b/pkgs/servers/home-assistant/custom-components/waste_collection_schedule/package.nix index 49a9e3b72496..9ace6725e542 100644 --- a/pkgs/servers/home-assistant/custom-components/waste_collection_schedule/package.nix +++ b/pkgs/servers/home-assistant/custom-components/waste_collection_schedule/package.nix @@ -20,13 +20,13 @@ buildHomeAssistantComponent rec { owner = "mampfes"; domain = "waste_collection_schedule"; - version = "2.25.0"; + version = "2.26.0"; src = fetchFromGitHub { inherit owner; repo = "hacs_waste_collection_schedule"; tag = "v${version}"; - hash = "sha256-LP8E5JBYiLu5UFxmkwht2noJLLqFzM9snuwhlbgdK2s="; + hash = "sha256-/kkqPV7Djp1IQ67nhkeboqzHrku/l6NWVlrf9+2wQ+c="; }; dependencies = [ From 3b59a80fd4a8b09355b03adf5814e09eb52ecb40 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 29 May 2026 07:49:45 +0200 Subject: [PATCH 55/55] aws-workspaces: 2025.0.5296 -> 2025.1.5526-1 Co-authored-by: RohanHart --- pkgs/by-name/aw/aws-workspaces/package.nix | 12 +++++++++-- .../aw/aws-workspaces/workspacesclient.nix | 21 +++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pkgs/by-name/aw/aws-workspaces/package.nix b/pkgs/by-name/aw/aws-workspaces/package.nix index 14c74efa0f12..d680759f8c61 100644 --- a/pkgs/by-name/aw/aws-workspaces/package.nix +++ b/pkgs/by-name/aw/aws-workspaces/package.nix @@ -5,7 +5,11 @@ buildFHSEnv, webkitgtk_4_1, ffmpeg_7, - gtk3, + gtk4, + libepoxy, + wayland, + libxcb, + libxi, pango, atk, cairo, @@ -48,7 +52,10 @@ buildFHSEnv { workspacesclient custom_lsb_release webkitgtk_4_1 - gtk3 + gtk4 + libepoxy + libxcb + libxi ffmpeg_7 pango atk @@ -56,6 +63,7 @@ buildFHSEnv { gdk-pixbuf protobufc cyrus_sasl + wayland ]; extraBwrapArgs = [ diff --git a/pkgs/by-name/aw/aws-workspaces/workspacesclient.nix b/pkgs/by-name/aw/aws-workspaces/workspacesclient.nix index 97f1d473c832..d50cd4c1ae14 100644 --- a/pkgs/by-name/aw/aws-workspaces/workspacesclient.nix +++ b/pkgs/by-name/aw/aws-workspaces/workspacesclient.nix @@ -5,25 +5,28 @@ dpkg, makeBinaryWrapper, glib-networking, + wrapGAppsHook4, + glib, }: let - dcv-path = "lib/x86_64-linux-gnu/workspacesclient/dcv"; + dcv-path = "lib/x86_64-linux-gnu/workspacesclient"; in stdenv.mkDerivation (finalAttrs: { pname = "workspacesclient"; - version = "2025.0.5296"; + version = "2025.1.5526-1"; src = fetchurl { urls = [ - "https://d3nt0h4h6pmmc4.cloudfront.net/ubuntu/dists/jammy/main/binary-amd64/workspacesclient_${finalAttrs.version}_amd64.deb" + "https://d3nt0h4h6pmmc4.cloudfront.net/ubuntu/dists/noble/main/binary-amd64/workspacesclient_${finalAttrs.version}_amd64.ubuntu2404.deb" ]; - hash = "sha256-VPNZN9AsrGJ56O8B5jxlgLMvrUViTv6yto8c5pGQc0A="; + hash = "sha256-iGYKpbpzGNeEUKgozhSwVd9dWRgQEbozIAWRu7wu2D8="; }; nativeBuildInputs = [ dpkg makeBinaryWrapper + wrapGAppsHook4 ]; installPhase = '' @@ -38,6 +41,12 @@ stdenv.mkDerivation (finalAttrs: { runHook postInstall ''; + preFixup = '' + schemadir=${glib.makeSchemaPath "$out" "$name"} + mv $out/share/workspacesclient/schemas/* $schemadir + glib-compile-schemas $schemadir + ''; + postFixup = '' # provide network support wrapProgram "$out/bin/workspacesclient" \ @@ -45,8 +54,8 @@ stdenv.mkDerivation (finalAttrs: { # dcvclient does not setup the environment correctly. # Instead wrap the binary directly the correct environment paths - mv $out/${dcv-path}/dcvclientbin $out/${dcv-path}/dcvclient - wrapProgram $out/${dcv-path}/dcvclient \ + mv $out/${dcv-path}/dcvviewer $out/${dcv-path}/workspacesclientdcv + wrapProgram $out/${dcv-path}/workspacesclientdcv \ --suffix LD_LIBRARY_PATH : $out/${dcv-path} \ --suffix GIO_EXTRA_MODULES : ${dcv-path}/gio/modules \ --set DCV_SASL_PLUGIN_DIR $out/${dcv-path}/sasl2 \