Work on "Let's Learn Nix: Dotfiles" blog post
It has been awhile since I have written a tutorial. I have spent 2-3 hours working on this post, but I think I need to spend another 2-3 hours before I publish it. I expect to be able to write these posts faster as I practice. I would like to create a few resources that I can reuse in each article for things like: - "Let's Learn Nix" reproducibility: Where I list all of the tutorials' dependencies: nix version, <nixpkgs> version, OS type and version, etc. - Haskell type signature convention for Nix - Ad hoc vs. declarative configuration for Nix - Troubleshooting Nix: <nixpkgs> search, nix repl, searching the Nix codebase
This commit is contained in:
parent
eb402bca74
commit
862c695900
1 changed files with 394 additions and 2 deletions
|
@ -1,9 +1,401 @@
|
|||
---
|
||||
title: "Let's Learn Nix: Dotfiles"
|
||||
date: 2020-03-13T22:23:02Z
|
||||
draft: false
|
||||
draft: true
|
||||
---
|
||||
|
||||
## Let's Learn Nix: Dotfiles
|
||||
|
||||
Coming soon...
|
||||
### 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
|
||||
|
|
Loading…
Reference in a new issue