forked from cadey/xesite
528 lines
17 KiB
Markdown
528 lines
17 KiB
Markdown
|
---
|
|||
|
title: "Nix Flakes: an Introduction"
|
|||
|
date: 2022-02-21
|
|||
|
tags:
|
|||
|
- nix
|
|||
|
- nixos
|
|||
|
series: nix-flakes
|
|||
|
author: Twi
|
|||
|
---
|
|||
|
|
|||
|
Nix is a package manager that lets you have a more deterministic view of your
|
|||
|
software dependencies and build processes. One if its biggest weaknesses out of
|
|||
|
the box is that there are very few conventions on how projects using Nix should
|
|||
|
work together. It's like having a build system but also having to configure
|
|||
|
systems to run software yourself. This could mean copying a NixOS module out of
|
|||
|
the project's git repo, writing your own or more. In contrast to this, [Nix
|
|||
|
flakes](https://nixos.wiki/wiki/Flakes) define a set of conventions for how
|
|||
|
software can be build, run, integrated and deployed without having to rely on
|
|||
|
external tools such as [Niv](https://github.com/nmattia/niv) or
|
|||
|
[Lorri](https://github.com/nix-community/lorri) to help you do basic tasks in a
|
|||
|
timely manner.
|
|||
|
|
|||
|
This is going to be a series of posts that will build on eachother. This post
|
|||
|
will be an introduction to Nix flakes and serve as a "why should I care?" style
|
|||
|
overview of what you can do with flakes without going into too much detail. Most
|
|||
|
of these will get separate posts (some more than one post).
|
|||
|
|
|||
|
In my opinion, here are some of the big reasons you should care about Nix
|
|||
|
flakes:
|
|||
|
|
|||
|
- Flakes adds project templates to Nix
|
|||
|
- Flakes define a standard way to say "this is a program you can run"
|
|||
|
- Flakes consolidate development environments into project configuration
|
|||
|
- Flakes can pull in dependencies from outside git repos trivially
|
|||
|
- Flakes can work with people that don't use flakes too
|
|||
|
- Flakes supports using private git repos
|
|||
|
- Flakes let you define system configuration alongside your application code
|
|||
|
- Flakes let you embed the git hash of your configurations repository into
|
|||
|
machines you deploy
|
|||
|
|
|||
|
## Project Templates
|
|||
|
|
|||
|
One of the big annoying parts about getting into Nix is that setting up projects
|
|||
|
isn't totally a defined science. Nix configurations just tend to grow
|
|||
|
organically and can easily become weird or difficult to understand for people
|
|||
|
that didn't start the project. Nix flakes helps fix this by doing a few things:
|
|||
|
|
|||
|
1. Defining a `flake.nix` as the central "hub" for your project's dependencies,
|
|||
|
exposed packages, NixOS configuration modules [and
|
|||
|
more](https://nixos.wiki/wiki/Flakes#Output_schema).
|
|||
|
2. Shipping a [set of templates](https://github.com/NixOS/templates) so that you
|
|||
|
can get projects started easily. Think something like
|
|||
|
[Yeoman](https://yeoman.io) but built directly into Nix. You can also define
|
|||
|
your own templates in your `flake.nix`.
|
|||
|
|
|||
|
As an example that we will use for the rest of this post to help explain it,
|
|||
|
let's make a Go project with their Go template. First you will need to enable
|
|||
|
Nix flakes on your machine. If you are using NixOS, add this to your
|
|||
|
`configuration.nix` file:
|
|||
|
|
|||
|
```nix
|
|||
|
nix = {
|
|||
|
package = pkgs.nixFlakes;
|
|||
|
extraOptions = ''
|
|||
|
experimental-features = nix-command flakes
|
|||
|
'';
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
Then rebuild your system and you can continue along with the article.
|
|||
|
|
|||
|
If you are not on NixOS, you will need to either edit `~/.config/nix/nix.conf`
|
|||
|
or `/etc/nix/nix.conf` and add the following line to it:
|
|||
|
|
|||
|
```
|
|||
|
experimental-features = nix-command flakes
|
|||
|
```
|
|||
|
|
|||
|
[You may need to restart the Nix daemon here, but if you are unsure how Nix was
|
|||
|
set up on that non-NixOS machine feel free to totally restart your
|
|||
|
computer.](conversation://Mara/hacker)
|
|||
|
|
|||
|
Now go to a temporary folder and run these commands to make a folder and create
|
|||
|
a new flake from a template:
|
|||
|
|
|||
|
```console
|
|||
|
mkdir ~/tmp/go-demo
|
|||
|
cd ~/tmp/go-demo
|
|||
|
nix flake new -t templates#go-hello .
|
|||
|
git init && git add .
|
|||
|
```
|
|||
|
|
|||
|
This will create a few files in the folder:
|
|||
|
|
|||
|
```console
|
|||
|
$ ls
|
|||
|
flake.lock flake.nix go.mod main.go
|
|||
|
```
|
|||
|
|
|||
|
Then you can look at `flake.nix` to see what's up:
|
|||
|
|
|||
|
```nix
|
|||
|
{
|
|||
|
description = "A simple Go package";
|
|||
|
|
|||
|
# Nixpkgs / NixOS version to use.
|
|||
|
inputs.nixpkgs.url = "nixpkgs/nixos-21.11";
|
|||
|
|
|||
|
outputs = { self, nixpkgs }:
|
|||
|
let
|
|||
|
|
|||
|
# Generate a user-friendly version number.
|
|||
|
version = builtins.substring 0 8 self.lastModifiedDate;
|
|||
|
|
|||
|
# System types to support.
|
|||
|
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
|
|||
|
|
|||
|
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
|
|||
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
|||
|
|
|||
|
# Nixpkgs instantiated for supported system types.
|
|||
|
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
|
|||
|
|
|||
|
in
|
|||
|
{
|
|||
|
|
|||
|
# Provide some binary packages for selected system types.
|
|||
|
packages = forAllSystems (system:
|
|||
|
let
|
|||
|
pkgs = nixpkgsFor.${system};
|
|||
|
in
|
|||
|
{
|
|||
|
go-hello = pkgs.buildGoModule {
|
|||
|
pname = "go-hello";
|
|||
|
inherit version;
|
|||
|
# In 'nix develop', we don't need a copy of the source tree
|
|||
|
# in the Nix store.
|
|||
|
src = ./.;
|
|||
|
|
|||
|
# This hash locks the dependencies of this package. It is
|
|||
|
# necessary because of how Go requires network access to resolve
|
|||
|
# VCS. See https://www.tweag.io/blog/2021-03-04-gomod2nix/ for
|
|||
|
# details. Normally one can build with a fake sha256 and rely on native Go
|
|||
|
# mechanisms to tell you what the hash should be or determine what
|
|||
|
# it should be "out-of-band" with other tooling (eg. gomod2nix).
|
|||
|
# To begin with it is recommended to set this, but one must
|
|||
|
# remeber to bump this hash when your dependencies change.
|
|||
|
#vendorSha256 = pkgs.lib.fakeSha256;
|
|||
|
|
|||
|
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
|
|||
|
};
|
|||
|
});
|
|||
|
|
|||
|
# The default package for 'nix build'. This makes sense if the
|
|||
|
# flake provides only one package or there is a clear "main"
|
|||
|
# package.
|
|||
|
defaultPackage = forAllSystems (system: self.packages.${system}.go-hello);
|
|||
|
};
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This defines a single Go package that is supported on macOS and Linux for 64 bit
|
|||
|
x86 processors and 64 bit ARM processors.
|
|||
|
|
|||
|
[In practice this spread should cover all of the main targets you'll need to
|
|||
|
care about for local development and cloud
|
|||
|
deployment.](conversation://Mara/hacker)
|
|||
|
|
|||
|
You can then build the flake with `nix build`:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix build
|
|||
|
```
|
|||
|
|
|||
|
And then run it:
|
|||
|
|
|||
|
```console
|
|||
|
$ ./result/bin/go-hello
|
|||
|
Hello Nix!
|
|||
|
```
|
|||
|
|
|||
|
## Standard Default Package
|
|||
|
|
|||
|
Let's take a closer look at the higher level things in the flake:
|
|||
|
|
|||
|
```nix
|
|||
|
{
|
|||
|
description = "A simple Go package";
|
|||
|
|
|||
|
inputs.nixpkgs.url = "nixpkgs/nixos-21.11";
|
|||
|
|
|||
|
outputs = { self, nixpkgs }: {
|
|||
|
packages = { ... };
|
|||
|
defaultPackage = { ... };
|
|||
|
};
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
[A note: in the rest of this article (and series of articles), when I refer to a
|
|||
|
"flake output", I am referring to an attribute in the `outputs` attribute of
|
|||
|
your `flake.nix`. Ditto with "flake input" referring to the `inputs` attribute
|
|||
|
of your `flake.nix`.](conversation://Cadey/enby)
|
|||
|
|
|||
|
When you ran `nix build` earlier, it defaulted to building the package in
|
|||
|
`defaultPackage`. You can also build the `go-hello` package by running this
|
|||
|
command:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix build .#go-hello
|
|||
|
```
|
|||
|
|
|||
|
And if you want to build the copy I made for this post:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix build github:Xe/gohello
|
|||
|
$ ./result/bin/go-hello
|
|||
|
Hello reader!
|
|||
|
```
|
|||
|
|
|||
|
A standard default package means that you can more easily build software without
|
|||
|
having to read documentation on what file to build. `nix build` will Just Work™️.
|
|||
|
|
|||
|
## Exposing Packages as Applications
|
|||
|
|
|||
|
Additionally, you can expose a package as an application. This allows you to
|
|||
|
simplify that above `nix build` and `./result/bin/go-hello` cycle into a single
|
|||
|
`nix run` command. Open `flake.nix` in your favorite editor and let's configure
|
|||
|
`go-hello` to be the default app:
|
|||
|
|
|||
|
```nix
|
|||
|
# below defaultPackage
|
|||
|
|
|||
|
defaultApp = forAllSystems (system: {
|
|||
|
type = "app";
|
|||
|
program = "${self.packages.${system}.go-hello}/bin/go-hello";
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
Then you can run it with `nix run`:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix run
|
|||
|
Hello Nix!
|
|||
|
```
|
|||
|
|
|||
|
Or you can run my copy:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix run github:Xe/gohello/main
|
|||
|
Hello reader!
|
|||
|
```
|
|||
|
|
|||
|
[What is that extra part of the URL path for? Is that a git
|
|||
|
branch?](conversation://Mara/hmm)
|
|||
|
|
|||
|
[Yes, you can use that syntax to set the git branch that Nix should build from.
|
|||
|
By default it will use the default branch (typically `main`), but sometimes you
|
|||
|
need to specify a branch or commit directly.](conversation://Cadey/enby)
|
|||
|
|
|||
|
## Development Environment Configuration
|
|||
|
|
|||
|
One of Nix's superpowers is the ability to declaratively manage the development
|
|||
|
environment for a project so that you can be sure that everyone working on the
|
|||
|
project is using the same tools.
|
|||
|
|
|||
|
[I use this with all of my projects to the point that when I am outside of a
|
|||
|
project folder I do not have any development tools
|
|||
|
available.](conversation://Cadey/enby)
|
|||
|
|
|||
|
Flakes has the ability to specify this using the `devShell` flake output. You
|
|||
|
can add it to your `flake.nix` using this:
|
|||
|
|
|||
|
```nix
|
|||
|
# after defaultApp
|
|||
|
|
|||
|
devShell = forAllSystems (system:
|
|||
|
let pkgs = nixpkgsFor.${system};
|
|||
|
in pkgs.mkShell {
|
|||
|
buildInputs = with pkgs; [ go gopls goimports go-tools ];
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
[I consider this to be a basic Go development environment. It includes standard
|
|||
|
tools such as the language server, `goimports` for better formatting and tools
|
|||
|
like <a href="https://staticcheck.io">staticcheck</a>. If you use staticcheck
|
|||
|
regularly at work, please consider throwing <a
|
|||
|
href="https://github.com/users/dominikh/sponsorship">Dominik</a> a couple bucks
|
|||
|
a month if you find it useful. It helps the project be more
|
|||
|
self-sustaining.](conversation://Mara/happy)
|
|||
|
|
|||
|
Then you can enter the development shell with `nix develop`:
|
|||
|
|
|||
|
```
|
|||
|
$ nix develop
|
|||
|
|
|||
|
[cadey@pneuma:~/tmp/gohello]$ go version
|
|||
|
go version go1.16.9 linux/amd64
|
|||
|
```
|
|||
|
|
|||
|
And then hack at your project all you want. You can send this git repo to a
|
|||
|
friend and they will have the same setup.
|
|||
|
|
|||
|
## External Dependencies
|
|||
|
|
|||
|
Now let's talk about inputs. Flake inputs let you add external dependencies to a
|
|||
|
project. As an example, let's look at the `nixpkgs` input:
|
|||
|
|
|||
|
```nix
|
|||
|
# Nixpkgs / NixOS version to use.
|
|||
|
inputs.nixpkgs.url = "nixpkgs/nixos-21.11";
|
|||
|
```
|
|||
|
|
|||
|
This defines the release of nixpkgs that should be used for the project. This
|
|||
|
template defaults to NixOS 21.11's version of nixpkgs, however we can upgrade it
|
|||
|
to nixos-unstable by changing it to this:
|
|||
|
|
|||
|
```nix
|
|||
|
# Nixpkgs / NixOS version to use.
|
|||
|
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
|||
|
```
|
|||
|
|
|||
|
Then we can run `nix flake update` and then `nix develop` and see that we are
|
|||
|
running a newer version of Go:
|
|||
|
|
|||
|
```console
|
|||
|
$ nix flake update
|
|||
|
warning: updating lock file '/home/cadey/tmp/gohello/flake.lock':
|
|||
|
• Updated input 'nixpkgs':
|
|||
|
'github:NixOS/nixpkgs/77aa71f66fd05d9e7b7d1c084865d703a8008ab7' (2022-01-19)
|
|||
|
→ 'github:NixOS/nixpkgs/2128d0aa28edef51fd8fef38b132ffc0155595df' (2022-02-16)
|
|||
|
|
|||
|
$ nix develop
|
|||
|
|
|||
|
[cadey@pneuma:~/tmp/gohello]$ go version
|
|||
|
go version go1.17.7 linux/amd64
|
|||
|
```
|
|||
|
|
|||
|
This also lets you pull in other Nix flakes projects, such as my CSS framework
|
|||
|
[Xess](https://github.com/Xe/Xess):
|
|||
|
|
|||
|
```nix
|
|||
|
inputs.xess.url = "github:Xe/Xess";
|
|||
|
inputs.xess.inputs.nixpkgs.follows = "nixpkgs";
|
|||
|
```
|
|||
|
|
|||
|
[Why is that second line needed?](conversation://Mara/hmm)
|
|||
|
|
|||
|
[By default when you pull in another project with Nix flakes, it treats that
|
|||
|
project as an entirely separate universe and only interacts with the outputs of
|
|||
|
that flake. This means it pulls in its own version of nixpkgs, each dependency
|
|||
|
it has can pull in that own version of nixpkgs and vice versa ad infinitum. By
|
|||
|
making Xess' nixpkgs input follows our own one, we are saying "I understand this
|
|||
|
may be incompatible, but please use this version of nixpkgs instead". This can
|
|||
|
help larger projects with many inputs (such as a nixos configs repo made by
|
|||
|
someone with too many throwaway side projects) evaluate and build faster. Nix
|
|||
|
flakes does have a cached evaluator, but still it helps to avoid the problem in
|
|||
|
the first place.](conversation://Cadey/enby)
|
|||
|
|
|||
|
Or anything you want! A useful library to pull in is
|
|||
|
[flake-utils](https://github.com/numtide/flake-utils), that can help you
|
|||
|
simplify your `flake.nix` and get rid of those ugly `forAllSystems` and
|
|||
|
`nixpkgsFor` functions in the `flake.nix` that this post used by default. For an
|
|||
|
example of a flake that uses this library, see [this
|
|||
|
`flake.nix`](https://tulpa.dev/Xe/mara/src/branch/main/flake.nix) from the IRC
|
|||
|
bot that lives in [`#xeserv`](https://web.libera.chat/#xeserv).
|
|||
|
|
|||
|
[Adapting this trivial example to use `flake-utils` is an excellent exercise for
|
|||
|
the reader!](conversation://Mara/happy)
|
|||
|
|
|||
|
## Backwards Compatibility
|
|||
|
|
|||
|
Normally you need to enable Nix flakes in your Nix daemon to take advantage of
|
|||
|
them. This is great for when you can do that, but sometimes you'll need to make
|
|||
|
things work for people without flakes enabled. This could happen when needing to
|
|||
|
graft in a Nix flakes project to one without flakes enabled. There is a library
|
|||
|
called [flake-compat](https://github.com/edolstra/flake-compat) that makes this
|
|||
|
easy.
|
|||
|
|
|||
|
Add the following to your flake inputs:
|
|||
|
|
|||
|
```nix
|
|||
|
inputs.flake-compat = {
|
|||
|
url = "github:edolstra/flake-compat";
|
|||
|
flake = false;
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
And then create `default.nix` with the following contents:
|
|||
|
|
|||
|
```nix
|
|||
|
(import (
|
|||
|
let
|
|||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
|||
|
in fetchTarball {
|
|||
|
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
|||
|
sha256 = lock.nodes.flake-compat.locked.narHash; }
|
|||
|
) {
|
|||
|
src = ./.;
|
|||
|
}).defaultNix
|
|||
|
```
|
|||
|
|
|||
|
And `shell.nix` with the following contents:
|
|||
|
|
|||
|
```nix
|
|||
|
(import (
|
|||
|
let
|
|||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
|||
|
in fetchTarball {
|
|||
|
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
|||
|
sha256 = lock.nodes.flake-compat.locked.narHash; }
|
|||
|
) {
|
|||
|
src = ./.;
|
|||
|
}).shellNix
|
|||
|
```
|
|||
|
|
|||
|
Then you can use `nix-build` and `nix-shell` like you have in other Nix
|
|||
|
projects.
|
|||
|
|
|||
|
## Private Git Repos
|
|||
|
|
|||
|
Nix flakes has native support for private git repositories as inputs. This can
|
|||
|
be useful when trying to build software you don't want to release as open to the
|
|||
|
world. To use a private repo, your flake input URL should look something like
|
|||
|
this:
|
|||
|
|
|||
|
```
|
|||
|
ssh+git://git@github.com:user/repo
|
|||
|
```
|
|||
|
|
|||
|
[I'm pretty sure you could use private git repos outside of flakes, however it
|
|||
|
was never really clear to me _how_ you end up doing
|
|||
|
it.](conversation://Cadey/coffee)
|
|||
|
|
|||
|
## Embed NixOS Modules in Flakes
|
|||
|
|
|||
|
The biggest ticket item for me is that it lets you embed NixOS modules in flakes
|
|||
|
themselves. This lets you define the system configuration for software right
|
|||
|
next to where the software is defined, thus shipping it as a unit. Using this
|
|||
|
you can make installing software a matter of adding it to your system's flake,
|
|||
|
adding the module and then enabling the settings you want to enable.
|
|||
|
|
|||
|
As an example, here is the NixOS module for that IRC bot I mentioned:
|
|||
|
|
|||
|
```nix
|
|||
|
nixosModules.bot = { config, lib, ... }: {
|
|||
|
options.within.services.mara-bot.enable =
|
|||
|
lib.mkEnableOption "enable Mara bot";
|
|||
|
|
|||
|
config = lib.mkIf config.within.services.mara-bot.enable {
|
|||
|
users.groups.mara-bot = { };
|
|||
|
|
|||
|
users.users.mara-bot = {
|
|||
|
createHome = true;
|
|||
|
isSystemUser = true;
|
|||
|
home = "/var/lib/mara-bot";
|
|||
|
group = "mara-bot";
|
|||
|
};
|
|||
|
|
|||
|
systemd.services.mara-bot = {
|
|||
|
wantedBy = [ "multi-user.target" ];
|
|||
|
environment.RUST_LOG = "tower_http=debug,info";
|
|||
|
unitConfig.ConditionPathExists = "/var/lib/mara-bot/config.yaml";
|
|||
|
serviceConfig = {
|
|||
|
User = "mara-bot";
|
|||
|
Group = "mara-bot";
|
|||
|
Restart = "always";
|
|||
|
WorkingDirectory = "/var/lib/mara-bot";
|
|||
|
ExecStart = "${self.defaultPackage."${system}"}/bin/mara";
|
|||
|
};
|
|||
|
};
|
|||
|
};
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
The key important part here is the `ExecStart` line. It points back to the
|
|||
|
flake's default package (which is hopefully where the bot's code is defined),
|
|||
|
and then has systemd manage that.
|
|||
|
|
|||
|
I plan to use this to radically simplify my nixos-configs repo. Right now it has
|
|||
|
a lot of code that is very project-specific and if I can move that into the
|
|||
|
projects in question, I can eliminate a lot of code out of the core of my
|
|||
|
configs repo.
|
|||
|
|
|||
|
## Embedding Configuration Git Hash into Systems
|
|||
|
|
|||
|
Finally, Nix flakes lets you see the configuration version of a system by
|
|||
|
embedding it at the build step. Normally NixOS lets you see the following
|
|||
|
information with `nixos-version --json`:
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"nixosVersion": "22.05pre348581.c07b471b52b",
|
|||
|
"nixpkgsRevision": "c07b471b52be8fbc49a7dc194e9b37a6e19ee04d"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
You have the NixOS version and the nixpkgs hash. That doesn't tell you what
|
|||
|
configuration you are running or anything about it though. However with flakes
|
|||
|
you can embed the git hash of your configuration into the system config:
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"configurationRevision": "f53891121ce4204f57409cbe9e6fcee3b030a350",
|
|||
|
"nixosVersion": "22.05.20220210.48d63e9",
|
|||
|
"nixpkgsRevision": "48d63e924a2666baf37f4f14a18f19347fbd54a2"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This can let you make a URL pointing to the commit in that output:
|
|||
|
|
|||
|
```console
|
|||
|
$ echo "https://tulpa.dev/cadey/nixos-configs/src/commit/$(ssh logos nixos-version --json | jq -r .configurationRevision)"
|
|||
|
```
|
|||
|
|
|||
|
Which will spit out a link to
|
|||
|
[cadey/nixos-configs@f53891121](https://tulpa.dev/cadey/nixos-configs/src/commit/f53891121ce4204f57409cbe9e6fcee3b030a350).
|
|||
|
|
|||
|
I'll cover more on how to do this in the NixOS deployment post.
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
There is a lot more to get into with each of these topics. I'm only really
|
|||
|
giving a very high level overview on them while I learn more and migrate over my
|
|||
|
NixOS configurations to flakes
|
|||
|
[piecemeal](https://tulpa.dev/cadey/nixos-configs). This has also given me the
|
|||
|
opportunity to clean things up and chew out a lot of the fat from my NixOS
|
|||
|
configurations. More to come when it is ready.
|