Proposal: Opt‑in “Impure Blocks” for Legacy/Compatibility Environments in NixOS

Hello NixOS team,

I’ve been using NixOS for a couple of years and appreciate its reproducibility, atomic upgrades, and rollback capabilities. Over time I’ve noticed a consistent gap: support for legacy binaries, proprietary drivers, and other software that fundamentally assumes a traditional FHS layout or mutable global paths.

Existing approaches like buildFHSUserEnv, nix‑ld, or containers/sandboxes help to some degree, but they are limited:

  • They are not first‑class config constructs.

  • They don’t integrate with NixOS’s activation/rollback model.

  • They don’t make it smooth or declarative to include legacy components in a system config.

At the same time, many users — especially those coming from distributions like Arch — want a middle ground:
an explicit, opt‑in impurity layer that provides a host‑integrated FHS‑like environment that:

  1. Is declared in NixOS config.

  2. Can contain packages or drivers that assume traditional paths (/bin, /usr/lib, /etc).

  3. Is tracked and participates in rollbacks.

  4. Doesn’t dilute the purity of the rest of the system.

One way to express this would be a new config construct, e.g.:

impure {
  name = "legacy-printer";
  packages = with pkgs; [ cups hplip legacy-driver ];
  fhsRoot = "/var/impure/legacy-printer";
  allowMutableEtc = true;
}

This would be conceptually similar to Rust’s unsafe { … } blocks: clearly marked zones of impurity, used only when necessary.

I believe this could address long‑standing pain points such as:

  • Legacy printer/scanner drivers that only ship for Debian/Fedora.

  • Vendor blobs or drivers that cannot be repackaged in a pure Nix way.

  • Tools or binaries that fundamentally rely on global mutable locations.

Importantly, this proposal doesn’t remove or weaken NixOS’s guarantees for the rest of the system — it simply provides a declared escape hatch that remains rollback‑aware and manageable.

I’ve searched upstream documentation and discussions, and while there are tools and techniques to approximate parts of this idea, there is no upstream support for a concept like this today. I think a well‑designed solution here could significantly broaden the practicality of NixOS for more users.

Happy to help with prototyping, documentation, or implementation discussion!

Thank you for your work on NixOS — it’s a unique and powerful system.

Best,

XanderTheDev

I think home-manager’s mkOutOfStoreSymlink allows this, but I am uncertain on having such function as a first-class contruct

It is more the idea. Does not have to be precisely this. But mostly just a way to make impure stuff work on nixos as an option. Just like rust has an option to make unsafe code that does not completely follow the rules of rust. So that you for example can make a legacy printer driver work easily instead of doing a lot of work to just port it over. And I do not know about that home manager thing you mentioned.

They don’t integrate with NixOS’s activation/rollback model.

  1. Is tracked and participates in rollbacks.

NixOS doesn’t manage state. When you do a rollback you’re just activating an older system, the state of your databases, logs, caches and everything else is unchanged. What happens next is up to the specific program: most simply don’t support schema downgrades, so they just break if there were incompatible changes.

What you’re describing is a problem not generally solvable, regardless of how well-behaved and declarative-configurable a program can be.

One way to express this would be a new config construct, e.g.:

The Nix language actually has an escape hatch that allows you to perform impure actions during evaluation, but this is not what you’re describing.

This would be conceptually similar to Rust’s unsafe { … }

Unlike Rust, Nix is a purely functional language, so it doesn’t have code blocks or control flow statements. impure { ... } would have to return a value, so it would be a normal function, exactly what pkgs.buildFHSEnv is, but IIUC instead of building the FHS environment in the Nix store, it would create it somewhere in your file system, so it’s not read-only.

This can be done without any Nix language change: the environment.etc option does something similar. Its implementation is an activation script that sets up some symlinks or copies files over to /etc.

I suppose that you don’t want the script to run on every activation, otherwise the environment will be reset when you reboot or switch configuration. So, the simplest solution would be a one-shot systemd service that runs the script when some file marker is missing.

But again, you can’t hope to manage the state (rollbacks) with NixOS.

2 Likes

If you write your own module, it is as first class within your config as the upstream ones. Presumably you’d be including some stuff specific to the programs in question…

You can use buildFHSUserEnv to create a package, you can wrap it in a script to pass the setup like mutable directories to mount, you can also refer in the generation of this script to a runCommand that unpacks whatever binaries you need. The resulting package using FHS envs will be perfectly integrated with rollbacks in terms of the versions used etc. (And NixOS does not have rollback integration for e.g. PostgreSQL state, so, no second-classness on that). And that will be as properly declarative as anything in Nixpkgs.

(For printing drivers specifically I guess integration with CUPS might be more complicated — but then it is a specific issue not covered by what are you asking here)

1 Like