alexandria/content/Hacks/nixos-cheat-sheet.md

6.9 KiB

id aliases tags title
nixos-cheat-sheet
nixos
nixpkgs
linux
Assorted NixOS hacks and tricks

The Nix package manager and NixOS Linux distribution are woefully underdocumented. There are many, many powerful features that are difficult to find, sans directly reading through source code.

I can't fix that, but this document serves as a personal cheat sheet of all the small Nix features that I don't want to forget.

Derivations

Stuff related to writing derivations of software.

Sparse Checkout with fetchGit (and friends)

You can do sparse checkouts when using fetchgit and its friends fetchFromGitHub, etc. This is useful if you're cloning a large repository and know you only need one specific directory. If you only need a specific file, consider fetchurl.

fetchgit {
   url = "https://github.com/foo/bar";
   hash = "sha256-0000000000000000000000000000000";

   # takes a list of directories that should be checked out
   sparseCheckout = ["dir", "another/dir", "foo/bar/dir"];
}

The callPackage pattern

This isn't even a note for myself since I use this feature so much, but I'm add it anyways so I can point to it as a reference.

When you look at package.nix files in the source code of nixpkgs, you'll see they typically take the form:

{
stdenv,
libfoo,
libbar,
fetchFromGitHub,
...
}:
stdenv.mkDerivation {
   nativeBuildInputs = [ stdenv libfoo libbar ];

   src = fetchFromGitHub {
      # blah blah
   };
   # rest of file omitted for brevity
}

It's a lambda (anonymous function) that takes in an attrset of everything the derivation needs, and returns the derivation. As you clearly see, the ... means that the attrset contains all of the attributes specified, but also arbitrarily more.

Exactly what attributes are available in this input attrset? It turns out that this attrset is actually populated with the entirety of nixpkgs. Basically, any package that can be referencd from pkgs.xxx is available in that input attrset.

This is very useful for writing clean derivations. Oftentimes I see people haphazardly pass pkgs around to all their derivations and directly make use of it. While this is fine in small derivations, it quickly gets messy in large ones. I prefer to always write non-trivial derivations using the nixpkgs pattern. However, how do we go from a file like the example above to an actual package that we can, say, output from a flake?

There's a function called pkgs.callPackage that handles precisely this task. It takes in two arguments. Here is its pseudo type signature:

callPackage :: file -> attrset -> package

The first argument is the file that contains the derivation as shown in the example. The second argument is an attrset that allows you to pass or overwrite any values in the attrset passed to argument 1.

Oftentimes the second argument is not even needed and so you simply pass an empty attrset ({ }).

packages.default = pkgs.callPackage ./my-derivation.nix { };

An example of the second argument in use is in a situation where you have a flake that provides multiple packages, but one of these packages depends on the other. Then, you can pass the packages to each other using the attrset argument.

packages = rec {
   foo = pkgs.callPackage ./foo.nix { inherit bar; };
   bar = pkgs.callPackage ./bar.nix { };
};

Then, in the corresponding derivation of foo, bar will be available as an input:

# file: foo.nix
{
   stdenv,
   libblah,
   bar, # the bar package we provided is now available here
   ...
}:
stdenv.mkDerivation {
   buildInputs = [ bar ];
}

Automatic updating derivations

Nix is powerful but also annoying at times. One of the most common issues is how to automatically keep a derivation up to date.

When you fetch remote files in Nix, you need to provide Nix with a hash so that it can guarantee the output is reproducible. This is a consequence of network only being available in the "fixed output derivation".

When we automatically update a Nix derivation's sources, we need to take care to both update the URL or revision and also the corresponding output hash.

The hash can be obtained using the nix store prefetch-file command, which replaces the old and inferior nix-prefetch-url.

However this command doesn't return a clean hash that we can just pipe around in our shell. We can add the --json flag and get a JSON object with the hash in the hash property. Such as the following:

# this is a nushell script because i hate jq
nix store prefetch-file https://my-file.com/file --json | from json | get hash

We can then take our new URL and hash and then update our derivation. One way to do this is with sed to modify the derivation in place, but this is kind of janky. Another way is to create a JSON file that contains the URL and hash and read it from Nix using builtins.readFile and builtins.fromJSON. This is much easier since you can trivially generate JSON files using shell commands (especially in Nushell).

The above process can be ran in CI to have fully automatic updating derivations. See nixos-ci for writing on Nix in CI.

nixpkgs and the nixpkgs lib

General stuff related to quirks in nixpkgs and lib.

Flakes

Niche features of Nix flakes.

Nix flake inputs have additional properties

Everyone knows the common properties of Nix flake inputs: the standard outputs like packages, apps, nixosModules, etc.

However, there a few that I have seen seldom used and hard to find mentions of in documentation

{
   inputs.cool-flake.url = "github:hackerman/cool-flake";

   outputs = 
      { self, cool-flake, ...}:
      {
         # gives the commit hash of the flake
         rev = cool-flake.rev;

         # gives the UNIX timestamp of the commit of the flake
         lastModified = cool-flake.lastModified;

         # you can also get these attributes on the current flake using `self`

         # gets current git commit hash
         # caveat: this property isn't defined if the git tree is dirty (there are uncommitted changes)
         selfRev = self.rev;

         # I often use this pattern due to the above reason
         selfRev' = if (self ? rev) then self.rev else "FALLBACK";
      }
}

NixOS

Things from the NixOS distribution, including the module system.

Misc

Stuff to do with other stuff. Potentially community made flakes.

Set the right time in Typix (and LaTeX)

When using Typix to compile Typst documents, sometimes it's a little too reproducible. Attempting to reference the current date and time in the Typst document will always return January 1st, 1980 when compiled in the Nix build environment.

You can set an environment variable to get the right time.

SOURCE_DATE_EPOCH = builtins.toString self.lastModified;

Here self is the self provided to the flake outputs that refers to the flake itself. As shown in ./nixos-cheat-sheet.md#Nix-flake-inputs-have-additional-properties, you can get the timestamp of the current Git commit.