Nix Flakes provide a great way to publish and maintain packages outside of nixpkgs, making it easier to “federate” the Nix experience in a sense.
The Problem
One major downside of publishing a custom flake (outside of nixpkgs) is no longer being able to take advantage of Hydra to cache your flake’s packages.
To work around this, the most common approach is to setup a custom cache, often with cachix (thanks @domenkozar!!!).
While it’s easy to setup a custom cache, it is difficult to make that cache accessible to users in a pleasant manner. In order for users to actually use your custom cache, they need to explicitly trust it.
It is important to note that the Hydra cache is special and does not require this extra step, as it is a trusted substituter by default across all Nix installations.
A user can explicitly trust your flake’s custom cache with one of the following approaches:
-
Update their Nix configuration to include your cache as a trusted substituter.
-
Interactively accept the substituter from extra Nix configuration (
nixConfig
) provided by the flake itself.
Both of these approaches currently have some major usability issues:
Problems with Updating Nix Configuration
- Requires loudly documenting this extra step within a package’s README or installation guide. If the user doesn’t happen to read this part of the documentation, they can end up spending hours waiting for your project to be built from scratch.
- Documenting this step clearly is verbose as the process of updating Nix configuration differs between NixOS (requires updating your NixOS configuration) and other platforms (requires updating
/etc/nix/nix.conf
). - Adding a cache to your trusted
substituters
in your Nix configuration means it will be checked for all packages and not just the package for which it is relevant. This is problematic as substituters appear to be checked sequentially, and each one takes multiple seconds before timing out. As a result, if you have more than 2 or 3 substituters listed in your configuration, in many cases it can take vastly longer to check for a package in your substituters than it takes to simply build the package from src, defeating the whole purpose.
Problems with Interactively Trusting Substituters
To clarify, this refers to the feature where Flake providers can specify a nixConfig
attribute that lists extra substituters in the flake itself. Doing so means that when a user uses your flake, they will be interactively prompted to accept these new substituters.
- This feature is still relatively new and severely under-documented. E.g. its existence is briefly mentioned in the Nix flakes wiki, but there is no commentary on how it works, how a user’s end configuration is resolved, or how any of the ambiguities around trust are handled. There are loads of Nix configuration attributes that make no sense for a Flake to specify on the user’s behalf.
- The interactive process breaks the declarative workflow.
nix build
no longer either works or doesn’t, instead a user must check their console for interaction. This makes automated workflows (e.g. in CI) that use the flake more complicated to setup. - The answers provided by the user must be stored somewhere, which means the build process for each flake becomes stateful. It is not obvious to the user how they can revoke access if they previously accepted, or give access if they previously denied. It is not clear whether or not these values are stored for the same flake when referred to as an input from other packages.
- Even if a user accepts the proposed configuration, there is a high chance that their acceptance is ignored without explanation. This is because by default, users are not a part of the
trusted-users
set, which is a requirement for the user to be able to accept the cache. Not only is this requirement not the default, but it is unclear to users whether they should add their own user totrusted-users
. E.g. is it safe? What if I only want to trust my user for this package and not all others? See this issue for details.
Potential Solution - Cache Lock / Committing binary hashes?
I wonder if there is an alternative way to allow Flakes to specify trusted binary hashes?
E.g. perhaps a flake could optionally commit a table of input hash → binary hashes for each platform?
Perhaps this could be a part of flake.lock
, or some dedicated flake.cache-lock
or similar?
The idea here would be to remove the need to interactively trust caches or touch local configuration, and avoid the need to fully trust caches and instead allow for validating binaries against a set of known hashes.
All that said, the workflow in maintaining such a lock for multiple platforms could be tricky. It’s simple enough to imagine how a flake.cache-lock
might be generated/updated as a part of nix build
, but updating these hashes for alternate platforms would likely be a lot more annoying (e.g. projects would need to come up with some way of performing these updates and generating these commits from other platforms on CI).
Please let me know if I’m missing some obvious, nicer approach to providing a nice user experience around flakes that provide custom caches! I’m still relatively new to grokking the details of Nix and providing custom caches, so there’s a good chance I’m missing some obvious insight.
Any thoughts/advice on this appreciated