- Declarative
- Reproducible
- Functional
- Pure
- Lazy
Tell the computer what to do,
users.spacekookie.homeDir = "/home";
not how to do it
# useradd spacekookie -d /home
This system works in layers!
- Your configuration sets a value (
users.spacekookie.homeDir = "/home";
) users
module provides options, and evaluates settings- Impure layer: (e.g.) run
useradd ...
, conditionally based on whether that user already exists in passwd file, etc
- Nix configuration changes are idempotent
users.spacekookie = { createHome = true; homeDir = "/home"; extraGroups = [ "wheel" "dialout" ]; };
- The same build inputs yield the same build outputs
- Build inputs are hashed, and can be re-used if already present
- If build inputs may change, build sare fixed by their output hash (more on that later)
build-01-set = [ 9gkyl3knyalavd5v77rb0ciwry1r4v77-foo gm1vihrf3d8hks2fgjfgfyn5wm2rs49a-bar ] build-02-set = [ 9gkyl3knyalavd5v77rb0ciwry1r4v77-foo psfi2l3kqpsp2zv66ngnaqhxnbzx1dn7-bar ]
Because the hash of the foo
input is the same, it can be re-used
from the store. The bar
package was updated, and thus needs to
be rebuilt!
- Usually located at
/nix/store
- Special permission setup
- Owned by
root:nixbld
- Write permission on
/nix/store
for owner and group - No write permissions for actual outputs (e.g.
/nix/store/<hash>-foo/
) - Additional
t
bit on/nix/store
, which means that removing a file requires write-permission on the file, not just the containing directory.
- Owned by
$ /n/s/zzg015adjliwmdm4jfkbhnkpw6dmq1ym-urxvt-autocomplete-all-the-things-1.6.0> tree -p
.
└── [dr-xr-xr-x] lib
└── [dr-xr-xr-x] urxvt
└── [dr-xr-xr-x] perl
└── [-r-xr-xr-x] autocomplete-ALL-the-things
3 directories, 1 file
The result of all this?
A nixbld
user can create a new directory and store build outputs,
but never remove or change them again! Build artifacts are
read-only!
- Functions have no side-effects
- Inputs map directly to outputs
let
makeYouUnderstand = rickAstley {
giveYouUp = "never"; letYouDown = "never";
runAround = "never"; desertYou = "never";
makeyouCry = "never"; sayGoodbye = "never";
tellALie = "never"; hurtYou = "never";
};
in
{ }
(no it doesn’t mean “it works”)
- Functions are first-class types in the Nix language
- Functions can be…
- passed as parameters
- returned as results
Syntax is Haskell inspired:
let cube = x: y: z: x * y * z;
in
(cube 2 4 8)
Expressions are only evaluated when they are needed for the result of an operation.
{
never = abort "<oh-no.jpg>";
use = "I'm a String";
}.use
As in: why go through all this trouble
- Dependency closures solve a lot of problems
- Closures only work in a pure environment
- Pure environments demand certain design principles (functional, lazy, reproducible)
We’ll get into the specifics but there are two scenarios that closures can cover.
Two rules if you will…
I’m building some software, and I don’t know the exact hash of what I will be producing! |
- You can do arbitrary computation
- Produce arbitrary outputs
- The build can not access the network!
I’m fetching a source archive. I know exactly what I will produce, so give me network access! |
- Only run once: will not be built again after the source is fetched
- Arbitrary computation is still possible, but bulider can not produce arbitrary outputs
- The exact hash of this build must be known ahead of time