Blog/why i use linux desktop (#138)
* bump deps * add nixos desktop flow post
This commit is contained in:
parent
2559274d95
commit
fb87addb26
|
@ -0,0 +1,533 @@
|
|||
---
|
||||
title: "My NixOS Desktop Flow"
|
||||
date: 2020-04-25
|
||||
series: howto
|
||||
---
|
||||
|
||||
# My NixOS Desktop Flow
|
||||
|
||||
Before I built my current desktop, I had been using a [2013 Mac Pro][macpro2013]
|
||||
for at least 7 years. This machine has seen me through living in a few cities
|
||||
(Bellevue, Mountain View and Montreal), but it was starting to show its age. Its
|
||||
12 core Xeon is really no slouch (scoring about 5 minutes in my "compile the
|
||||
linux kernel" test), but with Intel security patches it was starting to get
|
||||
slower and slower as time went on.
|
||||
|
||||
[macpro2013]: https://www.apple.com/mac-pro-2013/specs/
|
||||
|
||||
So in March (just before the situation started) I ordered the parts for my new
|
||||
tower and built my current desktop machine. From the start, I wanted it to run
|
||||
Linux and have 64 GB of ram, mostly so I could write and test programs without
|
||||
having to worry about ram exhaustion.
|
||||
|
||||
When the parts were almost in, I had decided to really start digging into
|
||||
[NixOS][nixos]. Friends on IRC and Discord had been trying to get me to use it
|
||||
for years, and I was really impressed with a simple setup that I had in a
|
||||
virtual machine. So I decided to jump head-first down that rabbit hole, and I'm
|
||||
honestly really glad I did.
|
||||
|
||||
[nixos]: https://nixos.org
|
||||
|
||||
NixOS is built on a more functional approach to package management called
|
||||
[Nix][nix]. Parts of the configuration can be easily broken off into modules
|
||||
that can be reused across machines in a deployment. If [Ansible][ansible] or
|
||||
other tools like it let you customize an existing Linux distribution to meet
|
||||
your needs, NixOS allows you to craft your own Linux distribution around your
|
||||
needs.
|
||||
|
||||
[nix]: https://nixos.org/nix/
|
||||
[ansible]: https://www.ansible.com/
|
||||
|
||||
Unfortunately, the Nix and NixOS documentation is a bit more dense than most
|
||||
other Linux programs/distributions are, and it's a bit easy to get lost in it.
|
||||
I'm going to attempt to explain a lot of the guiding principles behind Nix and
|
||||
NixOS and how they fit into how I use NixOS on my desktop.
|
||||
|
||||
## What is a Package?
|
||||
|
||||
Earlier, I mentioned that Nix is a _functional_ package manager. This means that
|
||||
Nix views packages as a combination of inputs to get an output:
|
||||
|
||||
<center>![A nix package is the metadata, the source code, the build instructions and
|
||||
some patches as input to a derivation to create a
|
||||
package](/static/blog/nix-package.png)</center>
|
||||
|
||||
This is how most package managers work (even things like Windows installer
|
||||
files), but Nix goes a step further by disallowing package builds to access the
|
||||
internet. This allows Nix packages to be a lot more reproducible; meaning if you
|
||||
have the same inputs (source code, build script and patches) you should _always_
|
||||
get the same output byte-for-byte every time you build the same package at the
|
||||
same version.
|
||||
|
||||
### A Simple Package
|
||||
|
||||
Let's consider a simple example, my [gruvbox-inspired CSS file][gruvboxcss]'s
|
||||
[`default.nix`][gcssdefaultnix] file':
|
||||
|
||||
[gruvboxcss]: https://github.com/Xe/gruvbox-css
|
||||
[gcssdefaultnix]: https://github.com/Xe/gruvbox-css/blob/master/default.nix
|
||||
|
||||
```nix
|
||||
{ pkgs ? import <nixpkgs> { } }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "gruvbox-css";
|
||||
version = "latest";
|
||||
src = ./.;
|
||||
phases = "installPhase";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -rf $src/gruvbox.css $out/gruvbox.css
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
This creates a package named `gruvbox-css` with the version `latest`. Let's
|
||||
break this down its `default.nix` line by line:
|
||||
|
||||
```nix
|
||||
{ pkgs ? import <nixpkgs> { } }:
|
||||
```
|
||||
|
||||
This creates a function that either takes in the `pkgs` object or tells Nix to
|
||||
import the standard package library [nixpkgs][nixpkgs] as `pkgs`. nixpkgs
|
||||
includes a lot of utilities like a standard packaging environment, special
|
||||
builders for things like snaps and Docker images as well as one of the largest
|
||||
package sets out there.
|
||||
|
||||
[nixpkgs]: https://nixos.org/nixpkgs/
|
||||
|
||||
```nix
|
||||
pkgs.stdenv.mkDerivation {
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
This runs the [`stdenv.mkDerivation`][mkderiv] function with some arguments in an
|
||||
object. The "standard environment" comes with tools like GCC, bash, coreutils,
|
||||
find, sed, grep, awk, tar, make, patch and all of the major compression tools.
|
||||
This means that our package builds can build C/C++ programs, copy files to the
|
||||
output, and extract downloaded source files by default. You can add other inputs
|
||||
to this environment if you need to, but for now it works as-is.
|
||||
|
||||
[mkderiv]: https://nixos.org/nixpkgs/manual/#sec-using-stdenv
|
||||
|
||||
Let's specify the name and version of this package:
|
||||
|
||||
```nix
|
||||
pname = "gruvbox-css";
|
||||
version = "latest";
|
||||
```
|
||||
|
||||
`pname` stands for "package name". It is combined with the version to create the
|
||||
resulting package name. In this case it would be `gruvbox-css-latest`.
|
||||
|
||||
Let's tell Nix how to build this package:
|
||||
|
||||
```nix
|
||||
src = ./.;
|
||||
phases = "installPhase";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -rf $src/gruvbox.css $out/gruvbox.css
|
||||
'';
|
||||
```
|
||||
|
||||
The `src` attribute tells Nix where the source code of the package is stored.
|
||||
Sometimes this can be a URL to a compressed archive on the internet, sometimes
|
||||
it can be a git repo, but for now it's the current working directory `./.`.
|
||||
|
||||
This is a CSS file, it doesn't make sense to have to build these, so we skip the
|
||||
build phase and tell Nix to directly install the package to its output folder:
|
||||
|
||||
```shell
|
||||
mkdir -p $out
|
||||
cp -rf $src/gruvbox.css $out/gruvbox.css
|
||||
```
|
||||
|
||||
This two-liner shell script creates the output directory (usually exposed as
|
||||
`$out`) and then copies `gruvbox.css` into it. When we run this through Nix
|
||||
with`nix-build`, we get output that looks something like this:
|
||||
|
||||
```console
|
||||
$ nix-build ./default.nix
|
||||
these derivations will be built:
|
||||
/nix/store/c99n4ixraigf4jb0jfjxbkzicd79scpj-gruvbox-css.drv
|
||||
building '/nix/store/c99n4ixraigf4jb0jfjxbkzicd79scpj-gruvbox-css.drv'...
|
||||
installing
|
||||
/nix/store/ng5qnhwyrk9zaidjv00arhx787r0412s-gruvbox-css
|
||||
```
|
||||
|
||||
And `/nix/store/ng5qnhwyrk9zaidjv00arhx787r0412s-gruvbox-css` is the output
|
||||
package. Looking at its contents with `ls`, we see this:
|
||||
|
||||
```console
|
||||
$ ls /nix/store/ng5qnhwyrk9zaidjv00arhx787r0412s-gruvbox-css
|
||||
gruvbox.css
|
||||
```
|
||||
|
||||
### A More Complicated Package
|
||||
|
||||
For a more complicated package, let's look at the [build directions of the
|
||||
website you are reading right now][sitedefaultnix]:
|
||||
|
||||
[sitedefaultnix]: https://github.com/Xe/site/blob/master/site.nix
|
||||
|
||||
```nix
|
||||
{ pkgs ? import (import ./nix/sources.nix).nixpkgs }:
|
||||
with pkgs;
|
||||
|
||||
assert lib.versionAtLeast go.version "1.13";
|
||||
|
||||
buildGoPackage rec {
|
||||
pname = "christinewebsite";
|
||||
version = "latest";
|
||||
|
||||
goPackagePath = "christine.website";
|
||||
src = ./.;
|
||||
goDeps = ./nix/deps.nix;
|
||||
allowGoReference = false;
|
||||
|
||||
preBuild = ''
|
||||
export CGO_ENABLED=0
|
||||
buildFlagsArray+=(-pkgdir "$TMPDIR")
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
cp -rf $src/blog $bin/blog
|
||||
cp -rf $src/css $bin/css
|
||||
cp -rf $src/gallery $bin/gallery
|
||||
cp -rf $src/signalboost.dhall $bin/signalboost.dhall
|
||||
cp -rf $src/static $bin/static
|
||||
cp -rf $src/talks $bin/talks
|
||||
cp -rf $src/templates $bin/templates
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
Breaking it down, we see some similarities to the gruvbox-css package from
|
||||
above, but there's a few more interesting lines I want to point out:
|
||||
|
||||
```nix
|
||||
{ pkgs ? import (import ./nix/sources.nix).nixpkgs }:
|
||||
```
|
||||
|
||||
My website uses a pinned or fixed version of nixpkgs. This allows my website's
|
||||
deployment to be stable even if nixpkgs changes something that could cause it to
|
||||
break.
|
||||
|
||||
```nix
|
||||
with pkgs;
|
||||
```
|
||||
|
||||
[With expressions][nixwith] are one of the more interesting parts of Nix.
|
||||
Essentially, they let you say "everything in this object should be put into
|
||||
scope". So if you have an expression that does this:
|
||||
|
||||
[nixwith]: https://nixos.org/nix/manual/#idm140737321975440
|
||||
|
||||
```nix
|
||||
let
|
||||
foo = {
|
||||
ponies = "awesome";
|
||||
};
|
||||
in with foo; "ponies are ${ponies}!"
|
||||
```
|
||||
|
||||
You get the result `"ponies are awesome!"`. I use `with pkgs` here to use things
|
||||
directly from nixpkgs without having to say `pkgs.` in front of a lot of things.
|
||||
|
||||
```nix
|
||||
assert lib.versionAtLeast go.version "1.13";
|
||||
```
|
||||
|
||||
This line will make the build fail if Nix is using any Go version less than
|
||||
1.13. I'm pretty sure my website's code could function on older versions of Go,
|
||||
but the runtime improvements are important to it, so let's fail loudly just in
|
||||
case.
|
||||
|
||||
```nix
|
||||
buildGoPackage {
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
[`buildGoPackage`](https://nixos.org/nixpkgs/manual/#ssec-go-legacy) builds a Go
|
||||
package into a Nix package. It takes in the [Go package path][gopkgpath], list
|
||||
of dependencies and if the resulting package is allowed to depend on the Go
|
||||
compiler or not.
|
||||
|
||||
[gopkgpath]: https://github.com/golang/go/wiki/GOPATH#directory-layout
|
||||
|
||||
It will then compile the Go program (and all of its dependencies) into a binary
|
||||
and put that in the resulting package. This website is more than just the source
|
||||
code, it's also got assets like CSS files and the image earlier in the post.
|
||||
Those files are copied in the `postInstall` phase:
|
||||
|
||||
```nix
|
||||
postInstall = ''
|
||||
cp -rf $src/blog $bin/blog
|
||||
cp -rf $src/css $bin/css
|
||||
cp -rf $src/gallery $bin/gallery
|
||||
cp -rf $src/signalboost.dhall $bin/signalboost.dhall
|
||||
cp -rf $src/static $bin/static
|
||||
cp -rf $src/talks $bin/talks
|
||||
cp -rf $src/templates $bin/templates
|
||||
'';
|
||||
```
|
||||
|
||||
This results in all of the files that my website needs to run existing in the
|
||||
right places.
|
||||
|
||||
### Other Packages
|
||||
|
||||
For more kinds of packages that you can build, see the [Languages and
|
||||
Frameworks][nixpkgslangsframeworks] chapter of the nixpkgs documentation.
|
||||
|
||||
[nixpkgslangsframeworks]: https://nixos.org/nixpkgs/manual/#chap-language-support
|
||||
|
||||
If your favorite language isn't shown there, you can make your own build script
|
||||
and do it more manually. See [here][nixpillscustombuilder] for more information
|
||||
on how to do that.
|
||||
|
||||
[nixpillscustombuilder]: https://nixos.org/nixos/nix-pills/working-derivation.html#idm140737320334640
|
||||
|
||||
## `nix-env` And Friends
|
||||
|
||||
Building your own packages is nice and all, but what about using packages
|
||||
defined in nixpkgs? Nix includes a few tools that help you find, install,
|
||||
upgrade and remove packages as well as `nix-build` to build new ones.
|
||||
|
||||
### `nix search`
|
||||
|
||||
When looking for a package to install, use `$ nix search name` to see if it's
|
||||
already packaged. For example, let's look for [graphviz][graphviz], a popular
|
||||
diagramming software:
|
||||
|
||||
[graphviz]: https://graphviz.org/
|
||||
|
||||
```console
|
||||
$ nix search graphviz
|
||||
|
||||
* nixos.graphviz (graphviz)
|
||||
Graph visualization tools
|
||||
|
||||
* nixos.graphviz-nox (graphviz)
|
||||
Graph visualization tools
|
||||
|
||||
* nixos.graphviz_2_32 (graphviz)
|
||||
Graph visualization tools
|
||||
```
|
||||
|
||||
There are several results here! These are different because sometimes you may
|
||||
want some features of graphviz, but not all of them. For example, a server
|
||||
installation of graphviz wouldn't need X windows support.
|
||||
|
||||
The first line of the output is the attribute. This is the attribute that the
|
||||
package is imported to inside nixpkgs. This allows multiple packages in
|
||||
different contexts to exist in nixpkgs at the same time, for example with python
|
||||
2 and python 3 versions of a library.
|
||||
|
||||
The second line is a description of the package from its metadata section.
|
||||
|
||||
The `nix` tool allows you to do a lot more than just this, but for now this is
|
||||
the most important thing.
|
||||
|
||||
### `nix-env -i`
|
||||
|
||||
`nix-env` is a rather big tool that does a lot of things (similar to pacman in
|
||||
Arch Linux), so I'm going to break things down into separate sections.
|
||||
|
||||
Let's pick an instance graphviz from before and install it using `nix-env`:
|
||||
|
||||
```console
|
||||
$ nix-env -iA nixos.graphviz
|
||||
installing 'graphviz-2.42.2'
|
||||
these paths will be fetched (5.00 MiB download, 13.74 MiB unpacked):
|
||||
/nix/store/980jk7qbcfrlnx8jsmdx92q96wsai8mx-gts-0.7.6
|
||||
/nix/store/fij1p8f0yjpv35n342ii9pwfahj8rlbb-graphviz-2.42.2
|
||||
/nix/store/jy35xihlnb3az0vdksyg9rd2f38q2c01-libdevil-1.7.8
|
||||
/nix/store/s895dnwlprwpfp75pzq70qzfdn8mwfzc-lcms-1.19
|
||||
copying path '/nix/store/980jk7qbcfrlnx8jsmdx92q96wsai8mx-gts-0.7.6' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/s895dnwlprwpfp75pzq70qzfdn8mwfzc-lcms-1.19' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/jy35xihlnb3az0vdksyg9rd2f38q2c01-libdevil-1.7.8' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/fij1p8f0yjpv35n342ii9pwfahj8rlbb-graphviz-2.42.2' from 'https://cache.nixos.org'...
|
||||
building '/nix/store/r4fqdwpicqjpa97biis1jlxzb4ywi92b-user-environment.drv'...
|
||||
created 664 symlinks in user environment
|
||||
```
|
||||
|
||||
And now let's see where the `dot` tool from graphviz is installed to:
|
||||
|
||||
```console
|
||||
$ which dot
|
||||
/home/cadey/.nix-profile/bin/dot
|
||||
|
||||
$ readlink /home/cadey/.nix-profile/bin/dot
|
||||
/nix/store/fij1p8f0yjpv35n342ii9pwfahj8rlbb-graphviz-2.42.2/bin/dot
|
||||
```
|
||||
|
||||
This lets you install tools into the system-level Nix store without affecting
|
||||
other user's environments, even if they depend on a different version of
|
||||
graphviz.
|
||||
|
||||
### `nix-env -e`
|
||||
|
||||
`nix-env -e` lets you uninstall packages installed with `nix-env -i`. Let's
|
||||
uninstall graphviz:
|
||||
|
||||
```console
|
||||
$ nix-env -e graphviz
|
||||
```
|
||||
|
||||
Now the `dot` tool will be gone from your shell:
|
||||
|
||||
```console
|
||||
$ which dot
|
||||
which: no dot in (/run/wrappers/bin:/home/cadey/.nix-profile/bin:/etc/profiles/per-user/cadey/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin)
|
||||
```
|
||||
|
||||
And it's like graphviz was never installed.
|
||||
|
||||
Notice that these package management commands are done at the _user_ level
|
||||
because they are only affecting the currently logged-in user. This allows users
|
||||
to install their own editors or other tools without having to get admins
|
||||
involved.
|
||||
|
||||
## Adding up to NixOS
|
||||
|
||||
NixOS builds on top of Nix and its command line tools to make an entire Linux
|
||||
distribution that can be perfectly crafted to your needs. NixOS machines are
|
||||
configured using a [configuration.nix][confignix] file that contains the
|
||||
following kinds of settings:
|
||||
|
||||
[confignix]: https://nixos.org/nixos/manual/index.html#ch-configuration
|
||||
|
||||
- packages installed to the system
|
||||
- user accounts on the system
|
||||
- allowed SSH public keys for users on the system
|
||||
- services activated on the system
|
||||
- configuration for services on the system
|
||||
- magic unix flags like the number of allowed file descriptors per process
|
||||
- what drives to mount where
|
||||
- network configuration
|
||||
- ACME certificates
|
||||
|
||||
[and so much more](https://nixos.org/nixos/options.html#)
|
||||
|
||||
At a high level, machines are configured by setting options like this:
|
||||
|
||||
```
|
||||
# basic-lxc-image.nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
networking.hostName = "example-for-blog";
|
||||
environment.systemPackages = with pkgs; [ wget vim ];
|
||||
}
|
||||
```
|
||||
|
||||
This would specify a simple NixOS machine with the hostname `example-for-blog`
|
||||
and with wget and vim installed. This is nowhere near enough to boot an entire
|
||||
system, but is good enough for describing the base layout of a basic [LXC][lxc]
|
||||
image.
|
||||
|
||||
[lxc]: https://linuxcontainers.org/lxc/introduction/
|
||||
|
||||
For a more complete example of NixOS configurations, see
|
||||
[here](https://github.com/Xe/nixos-configs/tree/master/hosts) or repositories on
|
||||
[this handy NixOS wiki page](https://nixos.wiki/wiki/Configuration_Collection).
|
||||
|
||||
The main configuration.nix file (usually at `/etc/nixos/configuration.nix`) can also
|
||||
import other NixOS modules using the `imports` attribute:
|
||||
|
||||
```nix
|
||||
# better-vm.nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./basic-lxc-image.nix
|
||||
];
|
||||
|
||||
networking.hostName = "better-vm";
|
||||
services.nginx.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
And the `better-vm.nix` file would describe a machine with the hostname
|
||||
`better-vm` that has wget and vim installed, but is also running nginx with its
|
||||
default configuration.
|
||||
|
||||
Internally, every one of these options will be fed into auto-generated Nix
|
||||
packages that will describe the system configuration bit by bit.
|
||||
|
||||
### `nixos-rebuild`
|
||||
|
||||
One of the handy features about Nix is that every package exists in its own part
|
||||
of the Nix store. This allows you to leave the older versions of a package
|
||||
laying around so you can roll back to them if you need to. `nixos-rebuild` is
|
||||
the tool that helps you commit configuration changes to the system as well as
|
||||
roll them back.
|
||||
|
||||
If you want to upgrade your entire system:
|
||||
|
||||
```console
|
||||
$ sudo nixos-rebuild switch --upgrade
|
||||
```
|
||||
|
||||
This tells nixos-rebuild to upgrade the package channels, use those to create a
|
||||
new base system description, switch the running system to it and start/restart/stop
|
||||
any services that were added/upgraded/removed during the upgrade. Every time you
|
||||
rebuild the configuration, you create a new "generation" of configuration that
|
||||
you can roll back to just as easily:
|
||||
|
||||
```console
|
||||
$ sudo nixos-rebuild switch --rollback
|
||||
```
|
||||
|
||||
### Garbage Collection
|
||||
|
||||
As upgrades happen and old generations pile up, this may end up taking up a lot
|
||||
of unwanted disk (and boot menu) space. To free up this space, you can use
|
||||
`nix-collect-garbage`:
|
||||
|
||||
```console
|
||||
$ sudo nix-collect-garbage
|
||||
< cleans up packages not referenced by anything >
|
||||
|
||||
$ sudo nix-collect-garbage -d
|
||||
< deletes old generations and then cleans up packages not referenced by anything >
|
||||
```
|
||||
|
||||
The latter is a fairly powerful command and can wipe out older system states.
|
||||
Only run this if you are sure you don't want to go back to an older setup.
|
||||
|
||||
## How I Use It
|
||||
|
||||
Each of these things builds on top of eachother to make the base platform that I
|
||||
built my desktop environment on. I have the configuration for [my
|
||||
shell][xefish], [emacs][xemacs], [my window manager][xedwm] and just about [every
|
||||
program I use on a regular basis][xecommon] defined in their own NixOS modules so I can
|
||||
pick and choose things for new machines.
|
||||
|
||||
[xefish]: https://github.com/Xe/xepkgs/tree/master/modules/fish
|
||||
[xemacs]: https://github.com/Xe/nixos-configs/tree/master/common/users/cadey/spacemacs
|
||||
[xedwm]: https://github.com/Xe/xepkgs/tree/master/modules/dwm
|
||||
[xecommon]: https://github.com/Xe/nixos-configs/tree/master/common
|
||||
|
||||
When I want to change part of my config, I edit the files responsible for that
|
||||
part of the config and then rebuild the system to test it. If things work
|
||||
properly, I commit those changes and then continue using the system like normal.
|
||||
|
||||
This is a little bit more work in the short term, but as a result I get a setup
|
||||
that is easier to recreate on more machines in the future. It took me a half
|
||||
hour or so to get the configuration for [zathura][zathura] right, but now I have
|
||||
[a zathura
|
||||
module](https://github.com/Xe/nixos-configs/tree/master/common/users/cadey/zathura)
|
||||
that lets me get exactly the setup I want every time.
|
||||
|
||||
[zathura]: https://pwmt.org/projects/zathura/
|
||||
|
||||
## TL;DR
|
||||
|
||||
Nix and NixOS ruined me. It's hard to go back.
|
|
@ -572,8 +572,8 @@
|
|||
fetch = {
|
||||
type = "git";
|
||||
url = "https://github.com/Xe/ln";
|
||||
rev = "v0.8.0";
|
||||
sha256 = "16wkjsbnn2ww7d6ihh6gaan8v3l9919qmx52jcjl5zx9w9y7yry6";
|
||||
rev = "v0.9.0";
|
||||
sha256 = "1djbjwkyqlvf5gy5jvx0z9mm3g56fg2jjmv0ghwzlvwwpx5h338l";
|
||||
};
|
||||
}
|
||||
]
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Loading…
Reference in New Issue