Nix 1.0 released: purely functional linux package manager
hydra.nixos.org
hydra.nixos.org
I think packakge managers should only handle the storage of static files like executables and shared libraries. Forcing the user or another process to handle configuration files and startup/shutdown hooks has additional benefits such as making these files easier to store in version control.
[1] http://lists.debian.org/debian-devel/2008/12/msg01027.html
However, when it comes to stateful stuff such as user data or databases, the funtional approach indeed doesn't really apply very well. NixOS manages state in an imperative manner in the same way as every other distribution. (For instance, NixOS uses Upstart jobs to create/update state.) That means that a rollback of your system won't rollback (say) your PostgreSQL database, unless somebody made the Upstart job of PostgreSQL do that. So when it comes to state, NixOS isn't better than other distributions, but it's not worse either ;-)
[1] http://nixos.org/, http://www.st.ewi.tudelft.nl/~dolstra/pubs/nixos-icfp2008-fi...
Suppose you have a networking library, which is used by several applications. All of a sudden you find a security hole in the networking library. And then you fix it. But the fix does not affect any of the published APIs or functionality and is thus completely backwards compatible for all legal uses.
When you fix the library you release a new version of it. Shouldn't that new version be automatically used for all applications that use the library? If you have an immutable packaging system, all applications will still use the old version of the library and they would have to have their packages explicitly modified to use the new version.
Now this may work for super high value and super secure systems, where someone has the time to individually test the new version of the library with each application that uses it and then individually update each application.
But for the usual desktop system this sounds like a recipe for having a bunch of out-dated hole ridden software, where multiple badly maintained applications still carry known security holes from many years ago; or, in other words, this sounds very much like Windows.
Instead of an implicit promise of compatibility, a Nix continuous build and testing system[2] could explicitly mark openssl-dependent packages as compatible by bumping their versions. The end user would need to update a lot more packages, but these cost could be reduced by binary diffing old and new packages, which should be almost identical with the exception of dependency metadata.
[1] http://www.youtube.com/watch?v=3aoDjW1bjxo [2] http://nixos.org/hydra/
What does change, though, is that you can opt to pass another version of the library to a specific application manually. If update from libA v1 to libA v2 breaks AppX, you can easily build AppX-with-libA-v1. It doesn't make any sense for security updates, but for major interface changes it sometimes provides a smoother migration path.
The relevant property is actually immutability, which looks nice.
While immutability and functional programming often go together, they aren't the same thing.
"Nix is a purely functional package manager. This means that it treats packages like values in purely functional programming languages such as Haskell — they are built by functions that don’t have side-effects, and they never change after they have been built"
(from the About Nix section on top of the page)
For programming languages, that means the result of a function is dependent on it's inputs ONLY. The function can not use any unspecified inputs like system time or an internal state.
In Nix, that means a package can only depend on dependencies which are specified beforehand. It's nearly impossible to use libraries or binaries which you did not specify.
That behavior makes it easier to reason about your whole system, like determining which packages are unused. This is similar to the advantages of referential transparency in functional languages
Not quite, it means that a function call can be replaced by its value (A function can depend only on its inputs, yet write to a global variable, for example).
http://hydra.nixos.org/build/2609700/download/1/manual/#id49...
From my cursory glance over nix's advantages, it sort of looks like the vagrant deployment system, but for package management. Or maybe something akin to Chef and Puppet (of which I have no familiarity with whatsoever).
I like these kind of snapshots, the ability to install multiple variants. But it's an old hat, no?
This bit is also interesting: "Runtime dependencies are found by scanning binaries for the hash parts of Nix store paths (such as r8vvq9kq…). This sounds risky, but it works extremely well."
It is the mutation of packages, silently breaking API and ABI compatibility, that is the bane of distro management. Nix is a great attempt to address this.
For proprietary software, you have to consider the binary as-distributed "source" and build a version with proper dynamic linker an library path. Some third party binary wouldn't run as-is on NixOS, but there is a package that sets correct linking settings post-factum.
If dependencies never change in place, you're emulating static linking with a dynamic linker.
Nix: A Safe and Policy-Free System for Software Deployment - http://www.st.ewi.tudelft.nl/~dolstra/pubs/nspfssd-lisa2004-...
I really hope nix will become standard practice, it has some significant advantages over existing package managers.
In this case it means that packages are handled as functional programming languages handle data values. New ones are created from old ones, and unreferenced values are cleaned up via garbage collection.
I don't know of any other distro that can pull that use case off right now. Or any other OS, really.
If my new kernel blew up my system, I don't go scrounging around for boot disks, I pick the previous image on next boot.
Debian and Fedora's package managers both keep the last three kernels operating and in grub.(This is a hassle for me since Fedora has had a long string of updates since 2.6.36 which was the last one that gave me working r/w HFS+ support, and i have to keep removing the second-to-last kernel manually with the package manager.)
"You can have multiple versions or variants of a package installed at the same time. This is especially important when different applications have dependencies on different versions of the same package — it prevents the “DLL hell”."
"Nix helps you make sure that package dependency specifications are complete."
"Nix has multi-user support. This means that non-privileged users can securely install software. "
"Since package management operations never overwrite packages in the Nix store but just add new versions in different paths, they are atomic ... And since package aren’t overwritten, the old versions are still there after an upgrade. This means that you can roll back to the old version"
"In addition to downloading binaries automatically if they’re available, Nix can download binary deltas that patch an existing package in the Nix store into a new version."
"Nix can be used not only for rolling out packages, but also complete configurations of services. This is done by treating all the static bits of a service (such as software packages, configuration files, control scripts, static web pages, etc.) as “packages” that can be built by Nix expressions. As a result, all the features above apply to services as well: for instance, you can roll back a web server configuration if a configuration change turns out to be undesirable, you can easily have multiple instances of a service (e.g., a test and production server), and because the whole service is built in a purely functional way from a Nix expression, it is repeatable so you can easily reproduce the service on another machine."
This sounds nice, and it's a direct quote from the product page, but I can guarantee that Nix does nothing of the sort. This would need to be addressed in ld.so. It's simply not possible for a packaging system to solve this problem on a typical unix system.
Variables can indeed bleed into sub-processes if you're not careful. If the sub-process's command is declared as a dependency then 0install could reset the environment automatically when starting it. Currently, it instead finds a set of libraries that work with the process and its dependencies (which is safe, but could make some cases unsolvable in theory, if a program wanted to run a subcommand which used an incompatible library).
Direct support from the dynamic linker would be nice though.
Variables cannot be set in the child process; the linker will have already run by the time you can access your environment. Furthermore, this scheme destroys the utility of LD_* variables, which itself is a non-starter.
All setuid binaries will be incompatible with any scheme which involves environment variables controlling the linker -- that's a core element of unix security architecture.
Over here [ http://news.ycombinator.com/item?id=3995200 ] someone who I believe is the author suggested that this thing uses DT_RPATH, and I've outlined the problems with that mechanism as well.
Perhaps I'm coming across as a curmudgeon, if so I apologize. But I've worked on this problem quite deeply and there's just no way to reliably solve it. Modifications to ld.so are an absolute requirement.
RPATH (or better RUNPATH) has intractable conflicts in library dependencies. For example, this cannot work for any application which uses dlopen() and packages its loadable modules independently of the invoking binary. This is a problem for very high profile software such as:
* Apache, and just about any other application server with a plugin interface * Perl, Python, Ruby, Java, and just about every other interpreted/VM oriented language
Furthermore, there exists other intractable conflicts in complex library dependency interactions. For example:
bin/A needs SONAME=B.so.1 needs SONAME=C.so.1 (v1)
bin/B needs SONAME=D.so.1 which needs both SONAME=C.so.1 (v2, incompatible) and SONAME=B.so.1
The dependency relationship of every shared object in this chain are not static, and you cannot modify the invoking binaries (where RPATH must be set) every time an intermediate shared object changes its linking requirements. You also cannot guarantee ordering when a single node depends on multiple libraries -- the order of evaluation is non-deterministic. You might say this is a corner case, but you would be surprised when you try at scale. This is not a corner case.
Seriously, I understand everything about this. Can't be done. You feel free to try it out and see that it doesn't.