Cover Image
Picture from Reddit

What?

As you might’ve guessed, I use Nix to manage most, if not all, of my machines and servers. Over time, I’ve encountered a few pain points—things that need to be done but aren’t well documented or are scattered across the internet.

So, I’ve decided to compile a list of what I learned in this short time.

Package? Hash? Yes!

We almost always see a sha256 hash in nix package definitions or expressions. This hash is used to verify the integrity of the file we’re downloading.

...
src = fetchFromGitHub {
  owner = "Moodkiller";
  repo = "MPV-Made-Easy";
  rev = "57b0a488e6a7238b46f94a472368973f77e1054c";
  hash = "sha256-FSIrs/4QVNOg7tJFKRmnx0eRRDKlDahYn/ckntkwmCg=";
};
...

Sometimes, it can be confusing on how-to determine one. To simplify this, we can use the nix-prefetch-url command and generate its hash, which can then be converted to the appropriate Nix format.

Tip

We can use both hashes in the package definition, but it’s recommended to convert it to the Nix format.

nix-prefetch-url --type sha256 --unpack file-url
# Output: 0k4w38v4jaygqm3xzjnx469jh64m43pqb6s6dwpdcylx9nbm2hvv
 
nix hash to-sri --type sha256 0k4w38v4jaygqm3xzjnx469jh64m43pqb6s6dwpdcylx9nbm2hvv
# Output: sha256-e0NRl02detYub0abhe8glRgokyHdyt9Hxc8rSTYanEw=

Adding to the above example, we can also use nurl:

nix run nixpkgs#nurl -- https://github.com/tomcur/termsnap termsnap-v0.3.0 2&>/dev/null

This will give us the following output, which can be directly used in our package definition:

fetchFromGitHub {
  owner = "tomcur";
  repo = "termsnap";
  rev = "termsnap-v0.3.0";
  hash = "sha256-FTgbbiDlHXGjkv3a2TAxjAqdClWkuteyUrtjQ8fMSIs=";
}

Flake outputs

Inspecting a flake’s output can be tedious, especially if it’s a large one. While the nix flake show command provides a quick top-level overview, using nix-inspect offers a more detailed and user-friendly view.

nix run nixpkgs#nix-inspect -- --path /path/to/flake

By using this, we can check if our values are being set correctly and if the flake is generating the desired output.

nix-inspect
nix-inspect output

Systemd services

Nix has a built-in module for managing systemd services and their configurations in nixpkgs, but in some cases, these lack certain configuration options.

For example, let’s see the popular web server Caddy. It’s service definition in the package doesn’t include an option to specify an environment file, so we can’t do something like this:

services.caddy = {
  enable = true;
  ...
  environmentFile = "/path/to/my.env";
  ...
};

This is a common use case, but we can’t achieve it with the default service definition. A workaround is to also define your service in the systemd.services list.

services.caddy.enable = true;
 
systemd.services.caddy = {
  serviceConfig = {
    EnvironmentFile = [ "/path/to/my.env" ];
  };
};

Custom helpers

We can also define custom helpers to make our configuration easier to manage. For example, we can create a basic one named enabled:

lib/module.nix
enabled = {
  ## Quickly enable an option.
  ##
  ## ```nix
  ## services.caddy = enabled;
  ## ```
  ##
  #@ true
  enable = true;
};

This just expands to enable = true, I use this thoroughly in my own configuration, for example:

nvim-lsp
helper usage in my lsp config

Enhanced tracing

Rebuilding a NixOS system typically doesn’t give much feedback, but using the --show-trace flag can provide detailed output. For an even more user-friendly experience, we can pair it with nix-output-monitor to get a visual and easier-to-read log.

nix run nixpkgs#nix-output-monitor -- build .#nixosConfigurations.#yourFlake.config.system.build.toplevel
# or if you have it installed
nom build .#nixosConfigurations.#yourFlake.config.system.build.toplevel

We can use this for any derivation, not just the system build, instead of nix build we just need to use nom build. It also shows us a dependency tree, which can be useful for debugging.

nix-output-monitor
nom build output for my raspberry pi

Devshell state

When using different shells like zsh, fish, or others, nix develop usually defaults to a basic bash shell, which can result in losing our current shell state and prompts. To avoid this, we can specify the shell to use with the -c option.

nix develop -c $SHELL

This command opens the development shell while preserving our current shell state.

Wrapping up

You can find some other useful tips in the NixOS Book or through my tags.

That’s all for now, I’ll try to write more of these as I learn more about Nix. If you have any suggestions, feel free to reach out to me.

Until next time! 👋