learn-flakes-the-fun-way/post.md
2024-07-08 12:18:24 -05:00

219 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This post is a Flake-based rewrite of [Learn Nix the Fun Way on fzakaria.com]
[0]. I really enjoyed the content of the post and wanted to write it as a Nix
user who is just using flakes. It does add a few extra steps and complexity,
but I think it's still valuable and perhaps reveals a bit more about Nix and why
it's pretty fantastic.
<!--more-->
## what-is-my-ip
Let's walk through a single example of a shell script one may write: _what-is-my-ip_
```bash
#!/usr/bin/env bash
curl -s http://httpbin.org/get \
| jq --raw-output .origin
```
Sure, it's _sort of portable_, if you tell the person running it to have _curl_ and _jq_. What if you relied on a specific version of either though?
Nix **guarantees** portability.
We might leverage _[Nixpkgs' trivial builders](https://ryantm.github.io/nixpkgs/builders/trivial-builders/)_ (specifically, `writeShellScriptBin`) in a basic Nix flake to turn this into a Nix derivation (i.e. build recipe).
```nix
# flake.nix
{
inputs.nixpkgs.url = "nixpkgs/nixos-24.05";
outputs = {nixpkgs, ...}: let
systems = ["aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux"];
pkgsFor = func: (nixpkgs.lib.genAttrs systems (system: (func (import nixpkgs {inherit system;}))));
in {
packages = pkgsFor (pkgs: {
default = pkgs.callPackage ./what-is-my-ip.nix {};
});
};
}
```
```nix
# what-is-my-ip.nix
{pkgs}:
pkgs.writeShellScriptBin "what-is-my-ip" ''
${pkgs.curl}/bin/curl -s http://httpbin.org/get | \
${pkgs.jq}/bin/jq --raw-output .origin
''
```
> 😬 Avoid over-focusing on the fact I just introduced a new language and a good chunk of boilerplate. Just come along for the ride.
Here we are pinning our package to dependencies which come from NixOS/Nixpkgs release branch 24.05.
If we `nix build` and `readlink result` we get:
**/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip**
And, of course, we can run our built result:
```console
$ ./result/bin/what-is-my-ip
24.5.113.148
```
Or run it from the Flake directly:
```console
$ nix run
24.5.113.148
```
Now that this is in Nix and we've modeled our dependencies, we can do _fun_ things like generate graph diagrams to view them (click the image to view larger).
```bash
$ nix-store --query --graph $(readlink result) | nix shell nixpkgs#graphviz -c dot -Tpng -o what-is-my-ip-deps.png
```
[![Image of what-is-my-ip dependencies as a graph](/assets/images/what-is-my-ip-deps.png)](/assets/images/what-is-my-ip-deps.png)
Let's create a _developer environment_ and bring in our new tool.
This is a great way to create developer environments with reproducible tools.
```diff
diff --git a/flake.nix b/flake.nix
index 2a99357..ab32421 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,11 +1,24 @@
{
inputs.nixpkgs.url = "nixpkgs/nixos-24.05";
- outputs = {nixpkgs, ...}: let
+ outputs = {
+ self,
+ nixpkgs,
+ ...
+ }: let
systems = ["aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux"];
pkgsFor = func: (nixpkgs.lib.genAttrs systems (system: (func (import nixpkgs {inherit system;}))));
in {
packages = pkgsFor (pkgs: {
what-is-my-ip = pkgs.callPackage ./what-is-my-ip.nix {};
});
+
+ devShells = pkgsFor (pkgs: {
+ default = pkgs.mkShell {
+ packages = [self.outputs.packages.${pkgs.system}.what-is-my-ip];
+ shellHook = ''
+ echo "Hello, Nix!"
+ '';
+ };
+ });
};
}
```
```console
$ nix develop -c $SHELL
Hello, Nix!
$ which what-is-my-ip
/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
```
🕵️ Notice that the hash **lr6wlz2652r35rwzc79samg77l6iqmii** is _exactly_ the same which we built earlier.
We can now do binary or source deployments 🚀🛠️📦 since we know the full dependency closure of our tool. We simply copy the necessary _/nix/store_ paths to another machine with Nix installed.
```console
$ nix copy --to ssh://beefcake $(nix build --print-out-paths)
$ ssh beefcake
[fmzakari@nixie:~]$ /nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
98.147.178.19
```
Maybe though you are stuck with Kubernetes or Docker. Let's use Nix to create an OCI compatible image.
```nix
let
pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {};
what-is-my-ip = import ./what-is-my-ip.nix {inherit pkgs;};
in
pkgs.dockerTools.buildImage {
name = "what-is-my-ip-docker";
config = {
Cmd = ["${what-is-my-ip}/bin/what-is-my-ip"];
};
}
```
```bash
docker load < $(nix-build what-is-my-ip-docker.nix)
Loaded image: what-is-my-ip-docker:c9g6x30invdq1bjfah3w1aw5w52vkdfn
docker run -it what-is-my-ip-docker:c9g6x30invdq1bjfah3w1aw5w52vkdfn
24.5.113.148
```
Cool! Nix + Docker integration perfectly. The image produced has only the files exactly necessary to run the tool provided, effectively **distroless**.
Finally, let's take the last step and create a reproducible operating system using NixOS to contain only the programs we want.
```nix
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz";
pkgs = import nixpkgs {};
what-is-my-ip = import ./what-is-my-ip.nix {inherit pkgs;};
nixos = import "${nixpkgs}/nixos" {
configuration = {
users.users.alice = {
isNormalUser = true;
# enable sudo
extraGroups = ["wheel"];
packages = [
what-is-my-ip
];
initialPassword = "swordfish";
};
system.stateVersion = "24.05";
};
};
in
nixos.vm
```
```console
nix-build what-is-my-ip-vm.nix
QEMU_KERNEL_PARAMS=console=ttyS0 ./result/bin/run-nixos-vm -nographic; reset
<<< Welcome to NixOS 24.05pre-git (x86_64) - ttyS0 >>>
Run 'nixos-help' for the NixOS manual.
nixos login: alice
Password:
[alice@nixos:~]$ which what-is-my-ip
/etc/profiles/per-user/alice/bin/what-is-my-ip
[alice@nixos:~]$ readlink $(which what-is-my-ip)
/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
[alice@nixos:~]$ what-is-my-ip
24.5.113.148
```
💥 Hash **lr6wlz2652r35rwzc79samg77l6iqmii** present again!
We took a relatively simple script through a variety of applications in the Nix ecosystem: build recipe, shell, docker image and finally NixOS VM.
Hopefully, seeing the _fun things_ you can do with Nix might inspire you to push through the hard parts.
There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.
**Learn Nix the fun way.**
[0]: https://fzakaria.com/2024/07/05/learn-nix-the-fun-way.html