commit 06c5be472a5e7ffc976147fb0dcd295674f15e88 Author: Daniel Flanagan Date: Mon Jul 8 09:35:53 2024 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4a847d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e5c11f7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-24.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2a99357 --- /dev/null +++ b/flake.nix @@ -0,0 +1,11 @@ +{ + 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: { + what-is-my-ip = pkgs.callPackage ./what-is-my-ip.nix {}; + }); + }; +} diff --git a/post.md b/post.md new file mode 100644 index 0000000..1d61b49 --- /dev/null +++ b/post.md @@ -0,0 +1,200 @@ +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. + + + +## 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: { + what-is-my-ip = 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 .#what-is-my-ip` and `readlink result` we get: + + **/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip** + +And, of course, we can run our result: + +```bash +$ ./result/bin/what-is-my-ip +24.5.113.148 +``` + +Or run it from the Flake: + +```bash +$ nix run .#what-is-my-ip +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 environment with reproducible tools + +```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.mkShell { + packages = [what-is-my-ip]; + shellHook = '' + echo "Hello, Nix!" + ''; + } +``` + +``` +❯ nix-shell what-is-my-ip-shell.nix +Hello, Nix! + +[nix-shell:~/tutorial]$ 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. + +```bash +❯ nix copy --to ssh://nixie.tail9f4b5.ts.net \ + $(nix-build what-is-my-ip.nix) --no-check-sigs + +❯ ssh nixie.tail9f4b5.ts.net + +[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 diff --git a/what-is-my-ip-deps.png b/what-is-my-ip-deps.png new file mode 100644 index 0000000..82246f1 Binary files /dev/null and b/what-is-my-ip-deps.png differ diff --git a/what-is-my-ip.nix b/what-is-my-ip.nix new file mode 100644 index 0000000..85ee401 --- /dev/null +++ b/what-is-my-ip.nix @@ -0,0 +1,5 @@ +{pkgs}: +pkgs.writeShellScriptBin "what-is-my-ip" '' + ${pkgs.curl}/bin/curl -s http://httpbin.org/get | \ + ${pkgs.jq}/bin/jq --raw-output .origin +''