about summary refs log tree commit diff
path: root/blog/content/english/lets-learn-nix-dotfiles.md
blob: 084fb19e44065797ad49b0b4f192ca4ccdf0177e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
---
title: "Let's Learn Nix: Dotfiles"
date: 2020-03-13T22:23:02Z
draft: true
---

## Let's Learn Nix: Dotfiles

### Dependencies

Speaking of dependencies, here's what you should know before reading this tutorial.

- Basic Nix syntax: Nix 1p

What version of Nix are we using? What version of `<nixpkgs>` are we using? What
operating system are we using? So many variables...

Cartesian product of all possibilities...

TODO(wpcarro): Create a graphic of the options.

### The problems of dotfiles

How do you manage your dependencies?

You can use `stow` to install the dotfiles.

### home-manager

What we are going to write is most likely less preferable to the following
alternatives:
- using Nix home-manager
- committing your `.gitconfig` into your

In the next tutorial, we will use [home-manager][wtf-home-mgr] to replace the
functionality that we wrote.

So why bother completing this?

### Let's begin

Welcome to the first tutorial in the [Let's Learn Nix][wtf-lln] series. Today we
are going to create a Nix derivation for one of your dotfiles.

"Dotfiles" refers to a user's collection of configuration files. Typically these
files look like:
- `.vimrc`
- `.xsessionrc`
- `.bashrc`

The leading "dot" at the beginning gives dotfiles their name.

You probably have amassed a collection of dotfiles whether or not you are
aware. For example, if you use [git][wtf-git], the file `~/.gitconfig` should
exist on your machine. You can verify this with:

```shell
$ stat ~/.gitconfig
```

When I was first learning `git`, I learned to configure it using commands I
found in books and tutorials that often looked like:

```shell
$ git config user.email
```

The `~/.gitconfig` file on your machine may look something like this:

```.gitconfig
[user]
	name = John Cleese
	email = john@flying-circus.com
	username = jcleese
[core]
	editor = emacs
[web]
	browser = google-chrome
[rerere]
	enabled = 1
	autoupdate = 1
[push]
	default = matching
[color]
	ui = auto
[alias]
	a = add --all
	ai = add -i
	b = branch
	cl = clone
	cp = cherry-pick
	d = diff
	fo = fetch origin
	lg = log --oneline --graph --decorate
	ps = push
	pb = pull --rebase
	s = status
```

As I ran increasingly more `git config` commands to configure my `git`
preferences, the size of my `.gitconfig` increased, and the less likely I was to
remember which options I set to which values.

Thankfully a coworker at the time, Ryan ([@rschmukler][who-ryan]), told me that
he version-controlled his `.gitconfig` file along with his other configuration
files (e.g. `.vimrc`) in a repository he called "dotfiles".

Version-controlling your dotfiles improves upon a workflow where you have a
variety of configuration files scattered around your machine.

If you look at the above `.gitconfig`, can you spot the dependencies?

We explicitly depend `emacs` and `google-chrome`. We also *implicitly* depend on
`git`: there is not much value of having a `.gitconfig` file if you also do not
have `git` installed on your machine.

Dependencies:
- `emacs`
- `google-chrome`

Let's use Nix to generate this `.gitconfig` file. Here is what I would like our
API to be:

Let's create a file `gitconfig.nix` and build our function section-by-section:

TODO(wpcarro): Link to sections here
- options.user
- options.core
- options.web
- options.rerere
- options.push
- options.color
- options.alias

```shell
$ touch gitconfig.nix
```

### options.user

```haskell
AttrSet -> String
```

```nix
user = {
  name = "John Cleese";
  email = "john@flying-circus.com";
  username = "jcleese";
};
```

```.gitconfig
[user]
	name = John Cleese
	email = john@flying-circus.com
	username = jcleese
```

### options.core

```nix
core = {
  editor = "${pkgs.emacs}/bin/emacs";
};
```

```.gitconfig
[core]
	editor = /nix/store/<hash>-emacs-<version>/bin/emacs
```

### options.web

```nix
web.browser = "${pkgs.google-chrome}/bin/google-chrome";
```

```.gitconfig
[web]
	browser = /nix/store/<hash>-google-chrome-<version>/bin/google-chrome
```

### options.rerere

```nix
rerere = {
  enabled = true;
  autoupdate = true;
};
```

```.gitconfig
[rerere]
	enabled = 1
	autoupdate = 1
```

### options.push

```nix
push.default = "matching";
```

```.gitconfig
[push]
	default = matching
```

### options.color

```nix
color.ui = "auto";
```

```.gitconfig
[color]
	ui = auto
```

We need to define a function named `gitconfig` that creates a Nix [derivation][wtf-derivation]:

```nix
# file: gitconfig.nix
let
  # Import the <nixpkgs> package repository.
  pkgs = import <nixpkgs> {};

  # Stringify the attribute set, `xs`, as a multilined string formatted as "<key> = <value>".
  # See attrsets.nix for more functions that work with attribute sets.
  encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);

  # Define out function name `gitconfig` that accepts an `options` argument.
  gitconfig = options: pkgs.stdenv.mkDerivation {
    # The gitconfig file that Nix builds will be located /nix/store/some-hash-gitconfig.
    name = "gitconfig";
    src = pkgs.writeTextFile ".gitconfig" ''
      [user]
          name = ${options.user.name}
          email = ${options.user.email}
          username = ${options.user.username}
      [core]
          editor = ${options.core.editor}
      [web]
          editor = ${options.web.browser}
      [rerere]
          enabled = ${if options.rerere.enabled "1" else "0"}
          autoupdate = ${if options.rerere.autoupdate "1" else "0"}
      [push]
          default = ${options.push.default}
      [color]
          ui = ${options.color.ui}
      [alias]
          ${encodeAttrSet options.aliases}
    '';
    buildPhase = ''
      ${pkgs.coreutils}/bin/cp $src $out
    '';
    installPhase = ''
      ${pkgs.coreutils}/bin/ln -s $out ~/.gitconfig
    '';
  };
} in gitconfig {
  user = {
    name = "John Cleese";
    email = "john@flying-circus.com";
    username = "jcleese";
  };
  core = {
    editor = "${pkgs.emacs}/bin/emacs";
  };
  web.browser = "${pkgs.google-chrome}/bin/google-chrome";
  rerere = {
    enabled = true;
    autoupdate = true;
  };
  push.default = "matching";
  color.ui = "auto";
  aliases = {
	a  = "add --all";
	ai = "add -i";
	b  = "branch";
	cl = "clone";
	cp = "cherry-pick";
	d  = "diff";
	fo = "fetch origin";
	lg = "log --oneline --graph --decorate";
	ps = "push";
	pb = "pull --rebase";
	s  = "status";
  };
}
```

### options.alias

We want to write a function that accepts an attribute set and returns a
string. While Nix is a dynamically typed programming language, thinking in types
helps me clarify what I'm trying to write.

```haskell
encodeAttrSet :: AttrSet -> String
```

I prefer using a Haskell-inspired syntax for describing type signatures. Even if
you haven't written Haskell before, you may find the syntax intuitive.

Here is a non comprehensive, but demonstrative list of example type signatures:
- `[String]`: A list of strings (i.e. `[ "cogito" "ergo" "sum" ]`)
- `AttrSet`: A nix attribute set (i.e. `{ name = "John Cleese"; age = 80; }`).
- `add :: Integer -> Integer -> Integer`: A function named `add` that accepts
  two integers and returns an integer.

Specifically, we want to make sure that when we call:

```nix
encodeAttrSet {
  a = "add --all";
  b = "branch";
}
```

...it returns a string that looks like this:

```.gitconfig
a = "add --all"
b = "branch"
```


TODO(wpcarro): @tazjin's nix-1p mentions this. Link to it.
Nix has useful functions scattered all over the place:
- `lib.nix`
- `list.nix`
- `lib.attrSet`

But I cannot recall exactly which functions we will need to write
`encodeAttrSet`. In these cases, I do the following:
1. Run `nix repl`.
2. Browse the Nix source code.

Google "nix attribute sets" and find the Github link to `attrsets.nix`.

You should consider repeating this search but instead of searching for
"attribute sets" search for "lists" and "strings". That is how I found the
functions needed to write `encodeAttrSet`. Let's return to our `nix repl`.

Load the nixpkgs set:

```nix
nix-repl> :l <nixpkgs>
Added 11484 variables.
```

Define a test input called `attrs`:

```nix
nix-repl> attrs = { fname = "John"; lname = "Cleese"; }
```

Map the attribute set into `[String]` using `lib.mapAttrsToList`:

```nix
nix-repl> lib.mapAttrsToList (k: v: "${k} = ${toString v}") attrs
[ "fname = John" "lname = Cleese" ]
```

Now join the `[String]` together using `lib.concatStringsSep`:

```nix
nix-repl> lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") attrs)
"fname = John\nlname = Cleese"
```

Now let's use this to define our function `encodeAttrSet`:

```nix
# file: gitconfig.nix
encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);
```

### Using nixpkgs search

[Nixpkgs search][wtf-nixpkgs-search].

### Conclusion

We learned how to help ourselves.

- Where does `emacs` exist? What about `google-chrome`? [nixpkgs search][wtf-nixpkgs-search]
- Verify that I have it? [nix REPL][using-nix-repl]

We used Nix to create our first derivation.

[wtf-lln]: /lets-learn-nix
[wtf-git]: https://git-scm.com/
[wtf-derivation]: https://nixos.org/nixos/nix-pills/our-first-derivation.html
[wtf-nixpkgs-search]: https://nixos.org/nixos/packages.html?channel=nixos-19.09
[using-nix-repl]: /using-the-nix-repl
[wtf-home-mgr]: https://github.com/rycee/home-manager
[who-ryan]: https://twitter.com/rschmukler