Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit44c5067285c5130dbc5926618fba8384813ac615 (patch)
Author
 Syndamia <kamen@syndamia.com>
2024-03-09 15:15:09 +0200
Committer
 Syndamia <kamen@syndamia.com>
2024-03-09 15:15:09 +0200
Files13bb50dba576535fd30a5acdc2de529a3190ad0e
syndamia.com-44c5067285c5130dbc5926618fba8384813ac615.tar
syndamia.com-44c5067285c5130dbc5926618fba8384813ac615.tar.gz
syndamia.com-44c5067285c5130dbc5926618fba8384813ac615.zip
Parent49282fe2b4bcf3c6d0e394c817d5231f9e217dda (diff)
(blog/yfno) Finished nix language information
1 files changed, 319 insertions, 14 deletions
-rw-r--r--src/blog/your-first-nixos-config/index.md333
diff --git a/src/blog/your-first-nixos-config/index.md b/src/blog/your-first-nixos-config/index.md
index 5c38fa2..3b41fcf 100644
--- a/src/blog/your-first-nixos-config/index.md
+++ b/src/blog/your-first-nixos-config/index.md
@@ -2,7 +2,7 @@
With a single [whitepaper](https://edolstra.github.io/pubs/phd-thesis.pdf), Eelco Dolstra created a package management system like no other.
By going against many of UNIX's core ideals, he delivered the *objecitvely* best way to configure and conduct a Linux-based machine.
-Though it's now without a price: the relatively steep learning curve.
+Though it's not without a price: the relatively steep learning curve.
I believe this curve is only steep because of the less-than-stellar documentation and resources available online, but it doesn't have to be.
@@ -12,41 +12,64 @@ Files on Linux are normally organized according to the [FHS](https://en.wikipedi
This system forces every binary and library to be "global", without much room for different versions of anything to exist at the same time.
In the days of old, this was perfectly fine, but in the modern era this ideal seems suffocating.
-As software programs have grown, so have their dependencies; before you would've needed less than a half-dozen libraries and external programs to create your application, but today that number can easily grow into the fifties for every-day executeables (like browsers).
+As software programs have grown, so have their dependencies; before you would've needed less than a dozen libraries and external programs to create your application, but today that number can easily grow into the fifties for every-day executeables (like browsers).
With an increased number of dependencies comes an increased amount of connectivity and inter-dependance between them.
As such, a minor misconfiguration or mismanagement of the hundreds of programs composing a modern system could easily form a crippling avalanche of unresolveable computer issues.
Here Nix comes to the rescue: what if every application/library/configuration lived in it's own completely separate folder, and you could chose what you needed, depending on the situation?
-Now you could have two (or more) applications with different versions of the same dependencies, coexisting together without issue.
-By extension, you could store entirely different systems, with different application sets and their subsequent configurations, on the same machine and switch between them with a single "command"[^grub].
+Now you could have two (or more) applications, each "connected" to different versions of the same dependencies, coexisting together without issue.
+
+By extension, you could store entirely different systems, with different application sets and their subsequent configurations, on the *same machine* and switch between them with a single "command"[^grub].
However, dependencies don't exist only when running an application, but also when building it!
And here we get another problem - modern build processes depend on a lot of "external" data, like special networked resources or global system information.
-One may be tempted to just ignore it (with great success), though we would simply be offloading the issue to another part in our system.
+One may be tempted to just ignore this issue (with relative success), though we would simply be offloading the problem to another part of our system.
The solution is "binary bit-by-bit reproduceability": everything your applications have access to during the build process is strictly constrained and controled in a reproduceable manner, with the goal of creating the **exact** same program, for the same dependencies (managing even "external" ones), every time.
+Another benefit is that if someone else has built your program, with the exact same parameters and isolated environment, then you can just copy their result, since it would be the exact same even if you built it!
+
+<br>
+
+For all of those reasons (and more) a Nix configuration is defined with a global set of files, specifying every characteristic of your setup.
+With these files you don't just include what packages you want installed, but also application-specific and hardware-specific options too.
+
+Long gone are the days of udev rules, copied from Arch forums, or arbitrary "application.conf" files in `/etc`, everything goes through your configuration in a consistent, clean and simple manner.
+
## The Nix language
-To provide a generic, unified and extensible way to describe such systems, we need a new programming language: Nix.
+&:warn This section is intended more as a reference.
+ It may be be better to continue to the [next one](TODO) and then come here when you don't understand something.
-It is dynamically typed, so you could abstract a lot of details from the user, functional, so your code would be as clean and powerful as possible, without sacrificing performance.
-But most importantly of all, Nix is lazy-evaluated, meaning nothing is executed, until something depends on it.
+To provide a generic, unified and extensible way to describe such systems, we need a new "programming" language: Nix.
+It is:
-The syntax itself is quite simple.
+1. dynamically typed, so you don't need to sweat the little details,
+2. functional, so you could "declare" what you need, without having to figure out how to do it and
+3. lazy-evaluated, so your PC computes only what it needs, keeping it fast and nimble
+
+For the uninitiated these may seem obscure, but the gist is that this language is made to be simple, easy and fast.
+Obviously, the syntax itself is also quite simple.
### Primitive data types
+These are the most basic types of values in our configuration.
There are five of them:
-- Number, could be a whole integer or a floating point number. There is also support for fancier number format, like TODO
+- Number, could be a whole integer or a floating point number.
+ There is also support for fancier number format, like `1.2e10`.
- Boolean, with the values `true` and `false`
- Null, which is the type of the value `null`
-- String, single-line string literals are done with double quotes: `"text"`
+- String, single-line string literals are done with double quotes:
+
+ ```nix
+ "text"
+ ```
+
while multi-line are done with `''`:
```nix
@@ -69,7 +92,7 @@ These are data types, which use other data types inside them:
- Attribute set, or more-commonly referred to as "hash-set" or "dictionary" in other languages.
Allows you to create a collection of items, but each item is identified with a String name.
- An attrset element is in the form `name = expression;` (don't forget the ending semicolumn!), and an attribute set is just a row of those, inside curly braces:
+ An attrset element is in the form `name = expression;` (don't forget the ending semicolumn!), and an attribute set is just a sequence[^attrset-values] of those, inside curly braces:
```nix
{ name1 = expression1; name2 = expression2; name3 = expression3; }
@@ -81,17 +104,299 @@ These are data types, which use other data types inside them:
{ a = 12; }.a -> returns the number 12
```
- Normally you can skip putting `"` around names inside attribute sets, but you must use them when the compiler can be confused:
+ Normally you can skip putting `"` around names inside attribute sets, but you must use them in unclear situations:
+
+ ```nix
+ { "=" = true; }."=" -> returns the value true
+ ```
+
+ It's very common to define attribute sets, holding attribute sets.
+ For this reason, Nix offers a simplified syntax, where dots inside the name signify sub-attrs, meaning the following:
+
+ ```nix
+ {
+ a.b.c = {
+ value1 = 5;
+ value2 = 6;
+ };
+ k.l = {
+ m.n = {
+ z.x = {
+ value3 = "Hello";
+ value4 = "World";
+ };
+ };
+ };
+ }
+ ```
+
+ is equivalent to:
```nix
- { "=" = true; }."="
+ {
+ a = {
+ b = {
+ c = {
+ value1 = 5;
+ value2 = 6;
+ };
+ };
+ };
+ k = {
+ l = {
+ m = {
+ n = {
+ z = {
+ x = {
+ value3 = "Hello";
+ value4 = "World";
+ };
+ };
+ };
+ };
+ };
+ };
+ }
+ ```
+
+ There is no limit on how "deep" you can go (the value `k` could also be written as `k.l.m.n.z.x = { ..... }`).
+
+ Obviously, if you surround your name with quotes, this feature won't happen, and the name will be taken as-is:
+
+ ```nix
+ { "a.b" = { v = 5; }; } -> attribute set with a single attribute by the name of "a.b"
```
### Constructs
+These are the "bigger" building blocks with which you would build a configuration file (program).
+
+- Let, allowing you to create local variables (assigning a name to a value) in a particular piece of code.
+ The generic syntax is:
+
+ ```nix
+ let name1 = expression1; name2 = expression2; name3 = expression3; in expression4;
+ ```
+
+ So it would look something like:
+
+ ```nix
+ let myval = { a = 123; }; in myval.a + 100; -> returns 223
+ ```
+
+- Conditional, allows you to return different values, depending on your case.
+ Generally:
+
+ ```nix
+ if boolean_expression then expression1 else expression2
+ ```
+
+ Where obviously, `boolean_expression` has to return a boolean.
+
+ For example (with the idea being, `20.05` and `21.11` are version numbers of your system):
+
+ ```nix
+ if systemname == "OldPC" then 20.05 else 21.11
+ ```
+
+ To make expressions which return boolean values, you need to combine [boolean operators](#operators).
+
+- Function, which takes a value as a parameter, and returns another value (possibly based on the one given).
+
+ ```nix
+ parameter_name: expression
+ ```
+
+ So, for example you could make a function which doubles the given number: `x: x * 2`, or one which doubles positive numbers and halves negative:
+
+ ```nix
+ n: if n > 0 then n * 2 else n / 2
+ ```
+
+ You give a function it's argument by just putting it at the end.
+ As to not cause confustion, you'll need to surround your function in braces:
+
+ ```nix
+ (n: n * n / 2) 5 -> returns the number 12
+ ```
+
+ Braces can be put anywhere, grouping whats inside as a single "item".
+
+ A function takes only **one** argument, but functions are [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen) and [currying](https://en.wikipedia.org/wiki/Currying) is supported, meaning you can return a function as if it was a value.
+ This is best explained with an example:
+
+ ```nix
+ a: b: a * b + b
+ ```
+
+ With the first value you pass, you'll get `b: a * b + b`, where `a` would be *replaced* with your given value.
+ On the second, you get the computed number.
+
+ ```nix
+ (a: b: a * b + b) 8 -> returns the function (b: 8 * b + b)
+ (a: b: a * b + b) 8 3 -> returns the number 27
+ ```
+
+ As a more-advanced example:
+
+ ```nix
+ let f1 = a: b: a * a + b * b - a / b;
+ f2 = f1 5.67;
+ in
+ (f1 5.67 19) == (f2 19); -> returns the boolean true
+ ```
+
+ <br>
+
+ Passing attribute sets to functions is a very common operation, so there is special syntax to ease use.
+ You can "destruct" an attrset, meaning declaring what names it must have, and using them as local variables.
+
+ ```nix
+ { name1, name2, name3 }: expression
+ ```
+
+ For an example, these two lines do the same thing:
+
+ ```nix
+ (as: as.a * as.b - as.c) { a = 3; b = 8; c = -2; }
+ ({ a, b, c }: a * b - c) { a = 3; b = 8; c = -2; }
+ ```
+
+ However, if the attrset has more names than is required, you will get an error.
+ You can add a special "name" at the end, the `...`, to specify that additional names must be ignored:
+
+ ```nix
+ ({ a }: a * a) { a = 5; b = 7; } -> returns an error
+ ({ a, ... }: a * a) { a = 5; b = 7; } -> returns the number 25
+ ```
+
+ Additionally, you would get an error if a required argument name isn't present.
+ The fix here is to use default values: names in the format `name ? expression` bind `name` to the value in the given attrset when it exists, but if it doesn't, they bind it to the value from `expression`:
+
+ ```nix
+ ({ a }: a * a) { b = 7; } -> returns an error
+ ({ a ? 3 }: a * a) { b = 7; } -> returns the number 9
+ ```
+
+ Finally, you can destruct an attrset while being able to reference the whole thing with a name, using either:
+
+ ```nix
+ { name1, name2, name3 }@attrset_name: expression
+ attrset_name@{ name1, name2, name3 }: expression
+ ```
+
+ Putting everything together in one big (dumb) example:
+
+ ```nix
+ let mysys = { name = "BigMachine"; configVersion = 1.3.0; nixVersion = 22.11; };
+ packetsCount = { name, ... }: if name == "BigMachine" then 3000 else 215;
+ nixVersion = { nixVersion ? 22.05, ... }@systeminfo: if systeminfo.name == "BigMachine" then 20.01 else nixVersion;
+ in
+ (packetsCount mysys) * (nixVersion mysys);
+ ```
+
+### Modifiers
+
+- By adding the keyword `rec` in front of an attribute set, you make it "recursive", meaning the previous elements are available as-is to the current one.
+
+ ```nix
+ { a = 123; b = a - 23; }.b -> returns an error, a isn't available inside b
+ rec { a = 123; b = a - 23; }.b -> returns the number 100
+ ```
+
+- Using `with` in a list allows all names of an attribute set to be used as local variables inside the list.
+ Generally:
+
+ ```nix
+ with attribute_set; list
+ ```
+
+ The following two lines do the same thing:
+
+ ```nix
+ [ someattr.a someattr.c ]
+ with someattr; [ a c ]
+ ```
+
+### Special features
+
+- Comments, arbitrary text (mostly to be read by humans).
+ Either single line, starting with `#`, or multiline with `/* */`:
+
+ ```nix
+ (a: a * a) 5 # Some comment here
+ /* Multiple
+ lines
+ of
+ comments */
+ ```
+
+- Interpolation, which allows you to put arbitraray expressions in the middle of strings (by extension, attribute names) and paths.
+ It's done by inserting `${expression}` where you want the text to be placed:
+
+ ```nix
+ let mysys = { name = "OldPC"; userdir = "/synd"; version = 23.11; };
+ msgname = "info";
+ in
+ {
+ homeconf = /home${mysys.userdir}/.config;
+ "${msgname}msg" = "This is machine ${mysys.name} on Nix ${mysys.version}";
+ };
+ ```
+
+- Inherit, which serves more as a quality-of-life feature than anything else.
+ Doing `inherit x` is the exact equivalent to doing `x = x`.
+ This is useful in `let` statements and attribute sets:
+
+ ```nix
+ x: let inherit x; in x + 2
+ let a = 6; in { inherit a; };
+ ```
+
+ These two examples are pretty dumb, but in the future you'll see cases where it comes in handy.
+
+ An even more useful version is when inheriting attributes from attribute sets, with the syntax:
+
+ ```nix
+ inherit (attribute_set) name1 name2 name3
+ ```
+
+ For example:
+
+ ```nix
+ (attrset: let inherit (attrset) x; in x + 5;) { x = 10; } -> returns the number 15
+ ```
+
+### Operators
+
+- There are the normal arithmetic ones: `+`, `-`, `*`, `/`
+
+- Normal boolean operators, like comparisons (`>`, `>=`, `==`, `<`, `<=`) or logical operators (`&&`, `||`, `!`, but also [material implication](https://en.wikipedia.org/wiki/Material_conditional) with `->`).
+ Boolean expressions are done by combining the comparison operators with the logical operators.
+
+- Concatenation, meaning appending one value to another.
+ For strings and paths `+` is used:
+
+ ```nix
+ "Hello" + ", " + "World" + "!" -> returns the string "Hello, World!"
+ /home/user + .config -> returns the path /home/user/.config
+ ```
+
+ For lists we use `++`:
+
+ ```nix
+ [ 1 ] ++ [ 2 3 ] ++ [ 4 ] -> returns the list [ 1 2 3 4 ]
+ ```
+
+- Attribute set update `//`, allows you to overwrite or add values to an existing attribute set:
+
+ ```nix
+ { a = 8; x = "hello" } // { a = 9; y = "world" } -> returns the attribute set { a = 9; x = "hello"; y = "world" }
+ ```
## The Nix package-manager
## NixOS configurations
[^grub]: In reality, if literally everything is different between those configurations, you would probably need to reboot and chose from your bootloader, but that's not significant now.
+[^attrset-values]: You could also have each element on a separate line, with leading (or trailing) spaces/tabs.