yaymukund’s weblog

Headless NixOS + Raspberry Pi + nixbuild from OSX!

I had to install NixOS on my Raspberry Pi 4, Model B recently. I didn’t have the HDMI→ micro HDMI cable so I decided to install it headlessly. This is a fairly intricate setup because I wanted to:

  1. Cross-compile from aarch64-darwinaarch64-linux.
  2. Remote build using nixbuild.net to speed up build times.
  3. Build from my memory-constrained 1GB Raspberry Pi.*

It involved a few gotchas which I want to document here.

* Note: Although it is possible to offload the compilation to nixbuild, you still need memory on the Pi to evaluate the nix code. There is an open issue for eval memory usage which may alleviate this.

Making a NixOS SD Image

First, make a flake.nix that produces the SD image:

{
  inputs = {
    nixos-generators.url = "github:nix-community/nixos-generators";
    nixos-hardware.url = "github:NixOS/nixos-hardware/master";
    nixpkgs.url = "nixpkgs/nixos-unstable";
  };

  outputs =
    { nixos-generators
    , nixos-hardware
    , nixpkgs
    }: {
      # This produces the install ISO.
      packages.aarch64-linux.installer-sd-image =
        nixos-generators.nixosGenerate {
          specialArgs = { inherit dotfiles-private; };
          system = "aarch64-linux";
          format = "sd-aarch64-installer";
          modules = [
            ./modules/hardware-configuration.nix
            nixos-hardware.nixosModules.raspberry-pi-4
            ./modules/base.nix
            ./modules/builder.nix
            ./modules/networking.nix
            ./modules/users.nix

            # Anything else you like...
          ];
        };
    };
}

Onto the modules…

Continue reading…

Using Nix on Flakes on OSX

I use Nix Flakes on OSX to setup my development environment. I’ve not seen anyone else document this approach. Maybe you will find it useful.

What’s in a development environment?

By “development environment,” I mean three things:

  1. Adding and mutating shell environment variables (e.g. $EDITOR)
  2. Installing command line applications (e.g. /usr/bin/nvim)
  3. Adding config files (e.g. $HOME/.config/nvim/init.lua)

Unfortunately, 2 and 3 are “impure” according to Nix because they require access to mutable paths. But there are simple workarounds:

So if I can mutate environment variables— including $PATH— then I can do everything!

But first, I need to explain Flakes a little bit.

A Nix Flakes primer

Sorry, I feel like every Nix article that touches on Flakes has to explain Flakes from scratch. I’ll try and stick with what’s relevant to what I’m doing. If you’re interested in a deep dive, I recommend Xe Iaso’s Nix Flakes: an Introduction.

Flakes, at their core, are a configuration format for the Nix toolchain. This format accepts inputs, which are dependencies that live in the Nix store, and produces outputs, which are read by various tools. For example, the nix CLI tool’s nix build subcommand builds the packages.default output for the flake.

See? That wasn’t so bad, was it? If this still seems a bit abstract, read on for an example.

Note: In versions of nix prior to 2.7, packages.default was known as defaultPackage. If you care about compatibility with old versions, you may want to alias it to point to packages.default.

Designing a development environment

Using Flakes, I need to mutate environment variables. To do this, I’ll use a little-known command called nix print-dev-env:

nix print-dev-env - print shell code that can be sourced by bash to reproduce the build environment of a derivation

If you run nix print-dev-env, it will build the packages.default output of your current flake.nix.

This approach has two steps:

  1. Make a packages.default output that mutates shell environment variables as desired. For example, it should add /nix/store/abc123-nvim-wrapped/bin to the $PATH.
  2. Source the output of nix print-dev-env in my development shell.

Putting the pieces together

To construct the packages.default output, you can use pkgs.mkShell:

# In flake.nix
let
  neovim-with-config = neovim.override {
    customRC = ''
      lua << EOF
        -- init.lua goes here
      EOF
    '';
  };
in 
  {
    outputs = flake-utils.lib.eachDefaultSystem (_system: {
        packages.default = pkgs.mkShell {
          packages = with pkgs; [
            neovim-with-config
            # anything else
          ];

          shellHook = ''
            # Optionally, inject other stuff into your shell
            # environment.
          '';
        };
      });
  }

Since the shell requires neovim-with-config, its ‘build environment’ will append /nix/store/abc123-neovim-with-config/bin/ to $PATH. That’s exactly what we want.

And finally, source the output of nix print-dev-env:

# `print-dev-env` assumes bash. It mutates env variables such as
# `LINENO` that # are immutable in zsh, so I need to exclude them.
# This is annoying, but in practice works fine.
$ nix print-dev-env \
  | grep -v LINENO \
  | grep -v EPOCHSECONDS \
  | grep -v EPOCHREALTIME \
  > $HOME/development-configuration.zsh 

$ echo 'source $HOME/development-configuration.zsh' >> $HOME/.zshrc

If you inspect development-configuration.zsh, you’ll see a giant RC file that includes:

PATH='...:/nix/store/abc123-neovim-with-config/bin:...'

Indeed, running nvim works as expected. We have set up a development environment using Nix Flakes!

Full dotfiles

If you want to see my full dotfiles, it lives on sourcehut. Here’s a link to where I define packages.default and here’s where I run print-dev-env

Scoring an animation with Orca

In this post, I’ll demonstrate how to score an animation using Orca.

Continue reading…

Restic + Backblaze B2 on NixOS

While NixOS fully supports making restic backups using Backblaze, I couldn’t find documentation for it. From browsing configs on GitHub, many people seem to also use rclone but I’d rather not introduce another dependency.

Here’s how I did it:

{ config, pkgs, ... }:
{
  environment.systemPackages = [ pkgs.restic ];

  services.restic.backups.myaccount = {
    initialize = true;
    # since this uses an `agenix` secret that's only readable to the
    # root user, we need to run this script as root. If your
    # environment is configured differently, you may be able to do:
    #
    # user = "myuser
    #
    passwordFile = config.age.secrets.my_backups_pw.path;
    # what to backup.
    paths = ["/home/myusername"];
    # the name of your repository.
    repository = "b2:my_repo_name";
    timerConfig = {
      # backup every 1d
      OnUnitActiveSec = "1d";
    };


    # keep 7 daily, 5 weekly, and 10 annual backups
    pruneOpts = [
      "--keep-daily 7"
      "--keep-weekly 5"
      "--keep-yearly 10"
    ];
  };

  # Instead of doing this, you may alternatively hijack the
  # `awsS3Credentials` argument to pass along these environment
  # vars.
  #
  # If you specified a user above, you need to change it to:
  # systemd.services.user.restic-backups-myaccount = { ... }
  #
  systemd.services.restic-backups-myaccount = {
    environment = {
      B2_ACCOUNT_ID = "my_account_id_abc123";
      B2_ACCOUNT_KEY = "my_account_key_def456";
    };
  };

}

Overriding packages in NixOS

In NixOS, it’s sometimes desirable to override a package in order to extend or modify its behavior. For example, I override my Neovim to add plugins so they get all the benefits of being in the nix store. Here’s how I do it.

Continue reading…

Rust Magic

This is a list of places in Rust where implementing a trait or using a struct affects the syntax of your code. I think of these features as “magical” because using them can change the behavior of very basic Rust code (let, for-in, &, etc.).

What follows is a small list of (hopefully) illustrative examples, and a short epilogue pointing you to more articles if this topic interests you.

Continue reading…

How to configure API Gateway v2 using Terraform

Here’s how you wire up an AWS lambda into an HTTP API using Terraform and AWS’s API Gateway v2 resources.

Continue reading…

What's in a name?

I decided to catalogue examples of engineers– primarily software developers– being asked to change a name to avoid being racist, sexist, transphobic, ableist, or otherwise bigoted. About half of these examples come from responses to my AskMetaFilter question.

Continue reading…

Panicking, unsafe, and you

In Jon Gjengset’s Demystifying Unsafe Code talk at Rust NYC, he gives a very interesting example of unsafe code. Here’s the link– please go and watch it– but I’ve transcribed it here along with my paraphrased explanation.

Continue reading…

A Few Things Like These (Ippatiyum Cila Vicayankal)

Among birds, I like crows very much.
It's true; it is a thieving creature
tactfully snatching away the eats from the hands of children.
In deed, it is a foolish creature
visiting and perching on the compound wall of the house
and caws at the oddest hours.
Even then
isn't it my friend
who looks at me and calls out to me
in my village where I crawled as a baby and grew up
and also in this city planted from elsewhere?

– Cinnakkapali (Translated by Nirmal Selvamony)

There were three or four crows standing on a branch in the trees outside our balcony, so this poem feels very timely.

Source: Oikopoetrics and Tamil Poetry by Nirmal Selvanomy