Purely Functional Linux with NixOS [video]
begriffs.com
begriffs.com
For example, our stack is nginx, uwsgi, django, elasticsearch, and postgres. Installing most of the pieces seemed relatively straightforward(though required a fair bit of searching through the nixpkgs source), but it's honestly pretty unclear how to install our actual django app.
Currently we just clone the repo to the server and point uwsgi to the relevant uwsgi.py file, but it doesn't seem like there's a good way to do that in NixOS.
I'd love to hear that I'm wrong here, but again, the lack of decent example documentation is really the single biggest issue I have with NixOS. The rest of the documentation seems to be improving slowly but steadily.
what i loved about habitat is the supervisor part. i there is a way to use habitat's supervisor with nix. then you get best of both tools.
until then i'll just have to stick to python's supervisord.
And in any rnrs complient Scheme, it's fairly trivial to make a strict functional dialect. And in Guile or Racket, it's even easier.
Unfortunately, Guix doesn't do this. Which meant Guix can't guarantee referential transparency. Although if the packager is an ANY WAY competant, it will be maintained.
No, it's not. It's a cut-down Scheme: Most of your scheme knowledge would apply directly, or indirectly.
Besides, even if it was a different language, it's a pre-existing one: DSSSL is pretty much this.
>If you're learning a new language, a new syntax isn't very hard.
Yes, but for me, the choice is learning a new language that has no other applications, save package managment, and using a subset of a language I already know.
>And there's not a terrible amount of people who think S-expressions are an inherently good syntax
I said from my perspective. I'm a schemer, so I'm already on board with sexprs.
Only if you regularly write code using purely functional scheme in the first place. The difficulty of writing in a new language is very rarely figuring out what function calls to make, at least for me - it's about thinking in terms of the language.
'((extensible-records . being-key-value-maps?))
Alists are fast enough for this, and they ARE k/v maps. And support for them is in rnrs. '(this is a list)
'((this . is) (an . alist))
Looks pretty distinct to me. '((this . is) (a . list) (of . cons))
'((this . is) (an . alist))
Hey look, my list of cons looks incredibly like an alist... funny that.and alist is a list of pairs. That's what it is. Semantically, there isn't really another reason to use the format.
(aside from Haskell and Scheme themselves, of course)
Those PureScript records tho...
They don't handle functions or infinite datastructures though.
- Guix refuses to integrate proprietary software so its usability is diminished compared to Nix (and Guix has less software packaged than Nix in the first place, AFAIK).
- Nix is a pretty good language for the task.
But this language is not a DSL, but a general purpose language that has pattern matchers, HTTP libraries, XML parsers, data structure implementations, etc.
>Guix refuses to integrate proprietary software so its usability is diminished compared to Nix (and Guix has less software packaged than Nix in the first place, AFAIK).
Guix is younger, thus has less software packaged, but the number of new packages introduced with each Guix release continues to increase. I have all the packages most important to me and have been using GuixSD exclusively for at least a year now.
>Nix is a pretty good language for the task.
The problem with any DSL is that is not very extensible and developers must spend time maintaining parsers, compilers, etc. for a one-off language. Guix is achieving its velocity in good part thanks to using an existing general purpose language that new languages can be embedded within.
I'm not expressing this as a criticism that I have, but for some people what you described is not a feature but a bug. :)
I don't know, I rather like Nix's language, maybe I am just not a Lisp person.
More practically, I can't think of anything that I wanted that Scheme offers but the nix expression language doesn't.
It may have a smaller stdlib than scheme (but I doubt it). But it's very hard to be simpler than scheme semantically.
As for what I want from scheme that nix doesn't provide, a simple, regular, syntax that I already know is what I want. I understand that that doesn't apply to everybody.
Yes, I mean the semantics of the language. Scheme is indisputably more complex. There isn't really much a standard library for the Nix Expression Language either.
> As for what I want from scheme that nix doesn't provide, a simple, regular, syntax that I already know is what I want. I understand that that doesn't apply to everybody.
off the top of my head:
Scheme-Simplified ::= <Item>*
Items ::= (define (<Id>*) <Expr>)
| (define <Id> <Expr>)
Expr ::= (<Expr> <Expr>*)
| (set! <Id> <Expr>)
| (lambda (<Id>*) <Expr>)
Nix-Expr ::= <Pattern> : <Nix-Expr>
| with <Nix-Expr>; <Nix-Expr>
| { <Nix-Item>* }
| let <Nix-Item>* in <Expr>
| <Expr> <ArithOp> <Expr>
| <Expr>.<Id>
| <Expr>.<String-Lit>
| <Expr> ? <Id>
| <Expr> ? <Id> or <Nix-Expr>
Nix-Item ::= <Id> = <Nix-Expr>;
| <StringLit> = <Nix-Expr>;
| inherit <Expr>;
| inherit (<Nix-Expr>) <Nix-Expr>; Scheme ::= <Expr>*
Expr ::= <Atom>
| (<Expr>*)
Atom ::= <String>
| <Symbol>
| <Number>
And IIRC, that's close to what r5rs requires, although without the '`, syntactic sugar, which a readtable would handle in schemes that have them, and would be added to the above for schemes that don't. I may be wrong, though.The semantics are also simple:
A function's arguments are evaluated, bound to arguments and passed to it. A special form or macro have their arguments passed to them unevaled. In the case of a macro, the output of the macro is evaled instead. Scope is lexical.
There's about 95% of the semantics right there. The only things I left out, to my knowledge, are dynamic variables, which weren't standardized until recently.
The entire thing may be larger than Nix (I don't think it is), but more regular? I doubt it.
Besides dynamic variables, there is also plain old variable mutation. Scheme macros are much more complex than what you described, because of hygiene. For the sake of making Scheme look better, I left macros out of my grammar. The Nix expression language has none of these things.
> but more regular? I doubt it.
Nix's lack of a top level (the <Items>* in Scheme) is a big boon for simplicity, especially concerning imports from other files which I forgot to include.
Fair enough, but:
>I should take a look at the guix APIs to craft a bigger subset.
What? No.
Those don't add any complexity to the language. You don't add every function to your BNF syntax, do you? This is what I meant about regularity: The sexpr and atoms are the only syntactic elements (except quote and friends, which are syntactic sugar for sexprs).
Most of the things you included in your scheme BNF syntax aren't syntax, they're special forms. Special forms are not syntactically distinct, they are semantically distinct. So while define, if, and lambda are part of the semantics, they aren't part of the syntax.
Macros, too, have no syntactic differentiation: You actually CAN'T add macros to the BNF of the syntax, because there is no syntax to add.
If that's not regular, I don't know what is.
>Nix's lack of a top level (the <Items>* in Scheme) is a big boon for simplicity, especially concerning imports from other files which I forgot to include.
How? What's so complex about having multiple forms at toplevel?
Oh, believe me, I'm all about the separation between language and library. I mean the smallest subset of Scheme necessary to use the api. For example, it might use string maps instead of association lists.
> Most of the things you included in your scheme BNF syntax aren't syntax, they're special forms.
This is just terminology. We have a hierarchy of languages here:
1. All terminating Scheme programs are Scheme programs
2. All Scheme programs are valid s-exprs
3. All s-exprs are plain text
4. All plain text is valid bytestring.
...
> Macros, too, have no syntactic differentiation: You actually CAN'T add macros to the BNF of the syntax, because there is no syntax to add.I was doing a grammar for scheme programs, not anything else on that list. Macros invocation requires its own non-terminals because its arguments are arbitrary sexprs, not merely unevaluated expressions.
> How? What's so complex about having multiple forms at toplevel?
Imports. file=expression makes Nix's `import <some-path>` dead simple. Scheme's is necessarily more complicated.
Look, first off, in a scheme system, there's no reason to use string maps. Symbols or strings can be used for alist lookup, and alists are plenty fast for these purposes.
Secondly, this is how a scheme experession is run, AFAIK.
(define foo (map odd? '(1 2 3 4)))
First off, in a scheme that has it, the readtable is run, transforming shortcuts like '<expr> into (quote <expr>). Then the expression is parsed into a memory representation of a sexpr. This could be a series of cons cells, but a lot of schemes use something else. What happens next is pretty implementation dependant, but in an interpreter, the resultant expression is evaluated. The evaluator sees the define - note this, in your model, the parser would catch this - dectects that it is a special form, and passes the expr to the code handing define. That code then evaluates the cddr of the expr, and binds the result to the symbol in the cadr. In some implementations, the cadr can be a list, but this isn't standard.Note that all the parser did was translate some syntactic sugar, and give the interpreter the datastructure representing the sexpr. Because that, AFAIK, is all the parser does.
I could be wrong, but tell me if I am, don't just go on as if I'd said nothing.
>I was doing a grammar for scheme programs, not anything else on that list. Macros invocation requires its own non-terminals because its arguments are arbitrary sexprs, not merely unevaluated expressions.
...An unevaled expression IS an arbitrary sexpr. What else can it be?
Using pre-compiled binaries means that builds aren't reproducible, a goal of both Nix and Guix.
Binaries can be signed to prove that the chain of trust is unbroken between the vendor (who maybe you trust enough to run their software on your systems) and you. In the case of a system with reproducible builds, that signature also says, "if you were to build this yourself, you'd end up with the same binary I am sending you."
Edit: I am beginning to realize that when davexunit says "pre-compiled binaries" they are not talking about an OS vendor (like Guix) distributing a binary package (with source available). They are talking about binary blobs provided by third parties, and in a form that is not reproducible, such as an nvidia driver kernel blob. This has led to me assuming davexunit is nuts, but apparently it is just that neither of us has been speaking very clearly to each other, and thus we've seemingly been talking past each other.
[0] https://reproducible-builds.org/
[1] https://www.gnu.org/software/guix/manual/html_node/Bootstrap...
Every binary in the chain can be signed and reproducible (theoretically; we aren't quite there, realistically speaking, and the bootstrap, as you note, is tricky). Forcing every user to build everything from source every time is just a mess, and provides little benefit over signed (reproducible) binaries for the vast majority of users.
It sounds like you (and the guix project) are conflating two orthogonal concepts and making "reproducible builds" into a concept that won't work in widespread use, and maybe even has negative value. We want reproducible builds so that we can be certain that the binary a vendor ships us is built exactly the way they say it was. We don't want it so that everyone is forced to build everything from scratch every time.
Reproducible builds means we can build an identical binary from source to prove that it is what we think it is. It should not mean we must build an identical binary from source every time we install a thing.
We don't want this either :) Let's not boil the oceans.
Nix and Guix are both a hybrid of build-from-source and binary package management systems. The package recipes and management tools describe how to build some software from source, and then transparently provide binaries for those recipes when the binary is available from a user-approved provider of the binaries.
But, by providing the full build recipes, all the way down to the bootstrap, users can easily rebuild binaries from source to check that their selected binary provider is honest, or to find reproducibility bugs.
In the case of a package recipe for some software that does not have source code available (a binary "blob"), the recipe would just download the blob from somewhere. We don't package those things in Guix, whereas Nix does. Although, it's not hard to make custom recipes that would do that, if you wanted to.
I have no problem with Guix being ideologically pure. Only with the notion that you can't have a reproducible build without forcing everyone to build it it themselves, which it seems is not the case with Guix. Cool.
>We don't want it so that everyone is forced to build everything from scratch every time.
I believe it is you who is confused. I never suggested this is what reproducible builds are about.
https://www.gnu.org/software/guix/manual/html_node/Substitut...
SwellJoe: Go back and substitute "source verifiable" every time davexunit says "reproducible" and everything he says makes sense.
davexunit: Read everyone else's comments from the POV where "reproducible" mean "reliable bitwise replication" without consideration of scientifically reproducible experiments or source verification, and everything they say makes sense.
I interpreted your statement of "Using pre-compiled binaries means that builds aren't reproducible, a goal of both Nix and Guix" to mean, "Guix does not distribute pre-compiled binaries, because they can't be reproduced". That still seems an entirely reasonable interpretation of that sentence, to me, as someone with no familiarity with Guix.
But, I now see that your intended meaning was that Guix does not distribute stuff like nvidia blobs, or Flash player, which is a perfectly reasonable choice for them, and nothing less than I would expect of a GNU project (and Fedora, my current favorite desktop distro, has the same policy, so I don't have a problem with it).
For example, the Haskell toolchain in nixpkgs downloads pre-compiled binaries of old GHC versions (7.0.4 and 7.4.2) which it uses to bootstrap the newer versions.
The binaries are checksummed to ensure reproducibility.
The same way: cryptographic signatures and checksums.
M-x passive-agressive-mode
And now, I'll go upgrade my NVIDIA drivers, and load up Psychonauts on Steam.https://github.com/genenetwork/guix-bioinformatics
and
https://github.com/Ecogenomics/ace-guix
I'm not sure how easy it is to maintain a system with extra packages like this, but it'd be nice to be able to use an "overlay" repo as you can in Gentoo for personal/custom packages.
Would love to ping davexunit to see if he can elaborate, because I don't see any specific way to add repositories from the documentation.
Is something similar/comparable planned to be added?
Orrr should someone step forward and volunteer to add it as a feature...?
(Don't worry, it's just a tiny toy gun.)
Although I do think that programmers often tend to try to solve problems with the kind of tools that they like to make, rather than ones that would be appropriate for the user -- and that's why the *nix open source world has so many incompatible programming languages and configuration formats, and so few good GUIs.
Use TOML, JSON, or YAML. If you need something general purpose, use Scheme. It works well embedded.
Distros like Ubuntu are still much easier to get running and this scares many people away :/
But, if it's not possible to custom build tiny containers on demand without needing a ton of memory, the power of it becomes less fun. I'm visualizing a system whereby you spin up custom environments based on need (with exactly and only the packages and config you need for the specific task, at hand), on demand, in a distributed environment. That'd be harder if the resources to spin up are very high.
apt and yum both can consume a lot of memory when installing large lists of packages, but 1GB is pretty far out, and you can work around it by installing a small number of packages at a time. Which slows it down, but allows installation to work on even very small systems (like 128MB, or even smaller, if you've got a little swap).
NixOS has support for hosting containers, which share the Nix store with the host, so they're presumably pretty lightweight.
I've tried running NixOS in VMs, using Nix on Ubuntu, orchestrating things with NixOps, etc. and found that it's much more painful than just installing NixOS on the bare metal and managing everything via /etc/nixos/configuration.nix
Compared to what?! There's no OS that has historically had better (or even close to as good) package management as Linux, either in the form of apt-get/dpkg or yum/RPM. Nix is probably better, on a couple of dimensions, but to suggest it was a mess before...well, I just wonder what you could be comparing to that would be better?
I.e. you download a zip file, you unpack it into a directory, and here is your entire program. Right there, in that directory, not splattered around the filesystem by the package manager.
That's the way it's done on Windows, anyway.
Not sure how things are these days, but i'm talking about the Win 9x to Win 7 days.
And I don't understand what kind of software you used with that pre-filter. You cannot even install drivers then.
But a lot of smaller programs are available in the zip form, or have alternatives that are.
In short, i prefer portable programs - https://en.wikipedia.org/wiki/Portable_application
But, the package manager knows where everything is so I don't have to (and there's consistency across systems...system python/perl/ruby/bash is always in /usr/bin).
If that's why "it's a mess", then I have to argue that it only looks like a mess, until you get to know it a little better. Package management is the single biggest reason I have never been able to seriously use any other desktop OS over the past 20 years, or so (I have a Windows partition for games and audio, and I've had a Hackintosh a couple of times for tinkering with iOS dev, but Linux is where I get work done).
It crushed my dreams of a small Windows partition + a large data/programs partition :(
Engineers shouldn't assume something is done well because nobody is doing any better. Some things suck and there's nothing to blame but other priorities or sheer laziness.
pkgsrc isn't bad at all, but definitely not better than yum or apt. One might prefer pkgsrc, but one couldn't reasonably say yum or apt are a "mess" compared to pkgsrc.
I love Nix and completely agree with that, and I do have lots of experience with several package managers (deb, rpm, rubygems, ...)
Awesome idea, and the rollbacks were great, but i kept having to write my own packages and it was all a bit too confusing to do that constantly.
NixOS works best when you're 'all-in' with Nix, including using nixkpkgs over language package managers (or having such package managers be nix-aware). This is not something that's easily done right now, and precludes having a 'pure' Nix experience, especially when using it for dev purposes.
In my case though, where the main application is a binary written in OCaml (which uses the opam package manager), NixOS makes it straightforward to build and distribute binary packages that are patched to the correct library paths on the system. Once that was set up, everything else was a breeze. Server maintenance is almost effortless.
Luckily b is almost fixed, and if combined with e.g. grsecurity I think nix has a good story.
It's still Linux but a lot of common problems go away. E.g. if you remove a user from your config then it's removed on the server (as opposed to Ansible).
Or: Because you have a dependency DAG all services that need restarting after a change are known (no more Chef notifies :restart).
Or: No more apt-get dist-upgrade. You can have a single package depend on an entirely new version of your OS and keep the old OS in place (except for the kernel version of course).
Edit: Just noticed the parent was comparing RHEL to Ubuntu, not NixOS, so now I'm not sure :).
I think it boils down to these points from the summary:
> Thus the same package can coexist on the system with multiple configurations.
That we have to worry about multiple static compilations of a package hits a deeper problem that Nix can't fix. Namely that there really shouldn't be any need for static install configurations. So the only static differentiation that should be necessary is version.
> The derivation is a string in key-value format which will ultimately be hashed and which can refer to objects in the Nix store
So why create a whole new "expression" language just to generate these? Developers work with key-value formats all the time (e.g. YAML, JSON). The whole functional language thing seems almost like a slight of hand when you realize this is the end result.
How does NixOS solve the diamond dependency problem?
If for some reason B and C depend on different versions of a library, then things may go wrong, e.g. one package may end up inadvertently using an incorrect version of a dependency. Nix cannot solve this problem because it originates from the OS/compiler design (same sonames, same symbol names). Consider that with respect to libraries, Nix mostly just wraps the compiler to add needed -L flags and also sets RPATH in shared libs and executables so that needed shared libs are found at runtime. If an application effectively depends on different versions of one library with the same soname, the dynamic linker will still use just one of the available ones.
What Nix does allow you to do is to isolate packages from one another. You can have one application using only one version of library D, and another application using only a different version of library D.
I should note that it's somehow hard to get to this situation, because the Nix packages collection (nixpkgs) doesn't keep too many versions of packages. Typically for minor upgrades the library is just bumped, and when you rebuild the final application all references to that library will be up to date. You can only realistically get issues if the application ends up depending on different major versions which are both maintained in nixpkgs but are not designed to work together in the same application.
I can imagine that package A may have two binaries, one depending on a library B version 1.1, and another, on B 1.2
Does NixOS allow to have both B-1.1 and B-1.2 installed, and A's different binaries to load the correct different versions of the library from B-1.1 and B-1.2?
If you think about it makes sense, glibc is still "just" a dependency so it needs to be references from dependant packages by a concrete /nix/store path. You wouldn't want to have a single global libc.so, because you want to be able to upgrade one application using different/newer OS components including libc without possibly breaking existing applications.
The ld.so path is basically the first thing you need to fix when packaging prebuilt software (using patchelf); the second thing are RPATH for other library dependencies.
EDIT: Yes the ld.so probably isn't patched, I failed to completely read your comment. If you want to know really you'd have to look into nixpkgs of course.
One notable thing Nix does is hardcode a path to a specific dynamic linker (e.g. /nix/store/HASH-glibc-2.23/lib/ld-linux-x86-64.so.2). All other dynamic libraries should be referenced through soname+RPATH.
It's a very interesting approach, and I want to understand it in detail. I haven't had the time to bring up a Nixos system yet, though.
> Does NixOS allow to have both B-1.1 and B-1.2 installed, and A's different binaries to load the correct different versions of the library from B-1.1 and B-1.2?
Yes, well actually, the thing is that typically you only "install" (into a user environment) final applications (A) that you need not libraries, so the libraries (B) can be considered to not really be installed, just available (somewhere in /nix/store) and referenced correctly (e.g. by RPATH).
[1] When you declare a dependency in a package specification by putting it into buildInputs, the compiler wrapper will automatically add an -L<store_path>/lib so that linking with just -l will find the library. With multiple versions of a library in buildInputs, that becomes a problem.
Imagine A depends on B and C, which both depend on D. The diamond dependency usually pops up when B and C need different versions of D.
But in this case, Nix allows you to simply install another version of B or C such that they can share D.
This doesn't help you if there's no set of A ... D that are jointly consistent, but then you would've been SOL anyway.
EDIT: I believe that my description is more accurate and if I am wrong I would like to be corrected. But I'd appreciate if you didn't downvote me simply because I use the term "GNU/Linux". I don't downvote you if you use the kernel's name for the entire OS. Thanks.