NixOS on Apple Silicon

Posted on Monday, Feb 13, 2023

A somewhat meandering experience report about running NixOS on an Apple Silicon Mac.

First, a bit of background. If you like, you can skip to the bit where I talk about actually using the thing.

Nix

About a year ago, I started getting interested in a programming language called Nix.

Nix is a very odd language. At a glance, it looks a bit like OCaml, bash, and JSON got tossed in a blender and swirled about for a while. Underneath the odd syntax, though, there's a pretty amazing super power: a (mostly) declarative, fully reproducible software build system. See, Nix isn't a general purpose programming language, although I suppose if you were mashochistic enough you could use it to do your taxes or whatever.

The big idea behind Nix is that if you precisely specify every input that's required to build a piece of software, you can get a deterministic, reproducible output. Which means that anyone else that has access to the same inputs can reliably build the exact same software artifact, on any machine, at any point in the future. This is a bigger deal than it may seem at first glance!

One of the implications of truly deterministic builds is that you can actually manage software dependencies in a way that doesn't make you want to chuck your computers into a lake and go join a monastery. The status quo for software dependencies is basically "Here's the name and version number of a library you'll need if you want to build this project. Hopefully whatever you have lying around is close enough!" With Nix, you can say "Use this exact build of this library, which I know for a fact works since it's exactly what I tested on my machine."

In other words, Nix is a solution to the "works on my machine" problem, which gets unfairly left out of the canonical lists of hard things in computer science.

Another benefit of reproducible builds is that binary caching is super effective! Nix will produce the same output on your laptop as it does on a build server, so you can safely grab prebuilt copies of every package in the system (unless you override the configuration for a package, which does require rebuilding things). All this adds up to one of the largest package repositories in the world, with over 80,000 packages for Linux, many of which are even available for macOS as well.

NixOS

There's a lot more to Nix as a language, but I've rambled on for a few paragraphs now and haven't got to what I actually wanted to talk about, which is NixOS on Apple's fancy new ARM chips.

NixOS is a Linux distribution that uses Nix for managing the entire system, including every piece of installed software, system services, hardware configuration, etc. This sounds a bit crazy, and in fact, it is! NixOS tosses out a ton of the conventions that most Linux systems rely on, including the layout of the filesystem. Instead of putting things into a bunch of special directories like /etc, /usr/bin, and so on, everything except your home directory goes into the "nix store" at /nix. There's still a /etc directory, since a lot of programs expect to find important things in there, but everything inside is a symbolic link to the real "source of truth" in the nix store.

The result is that NixOS is less an operating system than it is a hyper-specialized tool for creating operating systems. To install programs and manage the system, you write a configuration file in the Nix language, the output of which is a fully functional OS that has exactly what you specified and nothing else.

Since the currently running system is just a "projection" made out of symlinks, it's super easy to switch between configurations. If you make a change you don't like, you can just roll back to the previous config, which is happily hanging out in the nix store. This works even if you've hosed your system to the point where it won't boot any longer, as each "generation" gets its own boot loader entry, and you can just boot to the last working state.

Asahi Linux

Before I ramble on about Nix even more, lets talk Apple Silicon! A couple years ago, Apple started shipping Macs with custom ARM CPUs based on the chips they'd been using for iOS devices for a decade or so. There was much rejoicing, as the new chips are amazingly fast and power efficient and generally just wiped the floor with their predecessors from Intel.

Shortly after the new machines were released, a team of very clever people started working on getting Linux to run on them. The Asahi Linux project was created to reverse engineer the new hardware and make it play nice with Linux, and about a year later, the first alpha release was announced.

At the time, I was following along with interest, but things were a bit too early for me to dive in. I did toss a coin into the cup though, which gives me some warm fuzzies and helps the team out a bit.

Come December, the Asahi team gave us all a fabulous holiday present, in the form of a working driver for the Apple GPU. This is a truly amazing feat of reverse engineering (and forward engineering, for that matter). Not only did a tiny team of hackers figure out the magic incantations to drive an incredibly complex system with no public documentation, they also created the first ever Linux GPU driver written in rust. All while live streaming huge chunks of the work!

At this point, I was chomping at the bit to play with it, but what with one thing and another it took a few weeks before I had time to devote to the endevor. Luckily, an awesome person has been hard at work making NixOS run using the work from the Asahi devs, and by the time I got around to trying things out, they had already added support for the new GPU driver!

Experience report

Okay, that was a lot of preamble! So, what's it like?

In short, it's really nice, with a few rough edges. Let's start with the nice bits.

Install & configuration

Installation was straightforward using the excellent nixos-apple-silicon documentation. If you've never installed NixOS before, you'll likely also need to consult the NixOS manual, since the docs don't cover every detail. Apart from a few Asahi-specific things, the process was basically the same as installing NixOS on any other machine.

The default configuration that's bundled with the NixOS installer is pretty bare-bones, but it's enough to get started with. When setting up a new NixOS machine, I usually modify the default config a tiny bit to create a yusef user account and install vim and git, which is enough to clone my full config and tweak it.

I have a few NixOS machines at this point (several of them virtual), and they all build on the same base configuration with a few machine-specific tweaks. Basically, each host gets a .nix file that imports the base config, and I use NixOS options to customize things as needed.

For the Asahi install, I made an asahi.nix file for the host config, and I copied the /etc/nixos/hardware-configuration.nix file that was generated by the NixOS installer into my repo, so I can import it into asahi.nix.

The nixos-apple-silicon repo includes a support module that adds all the special sauce that makes the hardware work, so I needed to import that as well.

I'm using nix flakes, which is a way to structure nix projects that explicitly "locks down" all inputs, including the specific commit of nixpkgs and any other inputs that are referenced in your config. To get the asahi stuff into my config, I added a new input to my flake.nix file:

{
  inputs = {
    # other inputs not shown here...

    apple-silicon = {
      url = "github:tpwrules/nixos-apple-silicon";

      # this line prevents me from fetching two versions of nixpkgs:
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  # outputs, etc. down below...
}

To define a NixOS configuration using flakes, I use the lib.nixosSystem function via a little wrapper function I wrote called mkSystemConfig. The nixosSystem function has an attribute called specialArgs that will be made available to any .nix file in your config. I use this to make all the flake inputs available to the files that define each system, which lets me reference the apple-silicon input in the asahi.nix file.

Here's what my asahi.nix currently looks like, minus a few custom options that need more explanation than I have the juice for at the moment:

{ config, pkgs, lib, apple-silicon, ... }:
{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware/asahi.nix

      # apple-silicon hardware support
      apple-silicon.nixosModules.apple-silicon-support
      
      # include my base config
      ../default.nix
    ];

  # asahi linux overlay
  nixpkgs.overlays = [ apple-silicon.overlays.apple-silicon-overlay ];

  # enable GPU support
  hardware.asahi.useExperimentalGPUDriver = true;

  # backlight control
  programs.light.enable = true;  
  services.actkbd = {
    enable = true;
    bindings = [
      { keys = [ 225 ]; events = [ "key" ]; command = "/run/current-system/sw/bin/light -A 10"; }
      { keys = [ 224 ]; events = [ "key" ]; command = "/run/current-system/sw/bin/light -U 10"; }
    ];
  };

  # Use the systemd-boot EFI boot loader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = false;

  networking.hostName = "asahi"; # Define your hostname.
  networking.networkmanager.enable = true;

  system.stateVersion = lib.mkForce "23.05";
}

There are a few Apple silicon related things to call out.

First, I'm pulling in the apple-silicon flake input in the first line, so I can reference it in the config. In the imports list, I added apple-silicon.nixosModules.apple-silicon-support to pull in the hardware support module.

The line nixpkgs.overlays = [ apple-silicon.overlays.apple-silicon-overlay ]; adds an overlay that extends the default nixpkgs with asahi-specific stuff, like the patched version of mesa needed for GPU support.

Speaking of which, hardware.asahi.useExperimentalGPUDriver = true; enables the GPU driver using an option defined in the hardware support module.

The backlight control section configures keyboard shortcuts to control the screen brightness. If you're using a full desktop environment like KDE or Gnome, you might not need this, but I'm using sway, which doesn't deign to handle such things.

There's one more wrinkle I needed to sort out before building and applying the config. By default, the nixos-apple-silicon module loads firmware files from the /boot/asahi directory, which won't work with nix flakes by default. Flakes like to be completely "self contained," so reading things from the filesystem outside of the flake repo is verboten. You can make it work by copying the firmware files over, but I decided to just use the --impure flag when building the config to relax the rules a bit. Since I have a little shell script for switching configurations, I just made it pass in the flag if the /boot/asahi directory exists.

Once all that was sorted out, I had everything set up just the way I like it, with all my favorite programs and dotfiles waiting for me.

Things that rock

A bunch of stuff "just works," which is super impressive given how much work was needed to get here.

WiFi, Bluetooth, and USB work great, although full thunderbolt support is still in progress.

Sound works over the headphone jack and via Bluetooth, but the speakers are disabled to prevent damaging the hardware (seems like that's getting close though!). I did need to use alsamixer to set the output volume on the headphone jack - it seems to default to zero volume at boot. On my machine, you can use the amixer command from the alsa-utils package to set the volume to 100% with amixer -c 0 set "Jack DAC" 100%, which is a handy thing to put in startup script. Once the DAC volume is set, PulseAudio or Pipewire can control the volume for normal playback.

The touchpad works really well, especially under Wayland, where Firefox supports pinch-to-zoom and other gestures. Scrolling feels better than any Linux system I've used apart from Chromebooks, which have some special Google magic.

Battery life is excellent, and closing the lid enters "suspend to idle" mode, which puts the system in a low power state. It's not a full sleep state, and it does seem to drain a bit more than macOS when suspended, but it's pretty decent. I've been getting a full day's work out of this thing without worrying about running for the charger, so for me it's definitely "good enough."

Rough edges

There are a few things that aren't there yet, which seems worth a mention. Depending on when you're reading this these issues may have been sorted, so if you're interested, check out the feature support page on the Asahi wiki.

Perhaps the biggest missing piece is support for external displays. The HDMI port on MacBooks and DisplayPort alt mode for the USB-C ports are both still in progress. This isn't really an issue for me, since I have a seperate desktop machine and only use my MacBook in laptop mode, but if you use yours as a "portable desktop" you may want to hold off for a bit yet.

The speakers are another thing that are in the "nice to have" list for me, since I prefer to use headphones.

The GPU support is really sweet, but there's still some unsupported stuff and you'll probably run into a few glitches. So far the only things I've noticed are a few rendering issues in Firefox (e.g. the Plex web app has a bright pink background), and my go-to terminal emulator kitty needs a newer version of OpenGL than is currently supported. Alacritty runs great though, so it's all good :)

Wrapping up

Overall, I'm super happy with my experience so far. At the moment my NixOS partition is only about 150 GB (out of 1TB), but I'm seriously considering shrinking the macOS install down to a few hundred gigs and using Linux as my daily driver.

There's something about being able to completely control every aspect of the operating system that really appeals to me. I still like macOS for the most part, but I realized a couple years ago that I no longer look forward to new OS updates, and instead I always have a sort of gloomy "I wonder what they changed this time" feeling. Maybe I'm just getting older and less adaptable, or maybe I'm just not the target audience for macOS any longer.

As for NixOS vs other Linux distros, it definitely has a steep learning curve and isn't for everyone. But for me, being able to check my entire OS configuration into git and go from a clean install to having everything set up exactly as I like it in a few minutes makes me super happy, and now that I'm used to it there's no going back.

If you found this post interesting, I'd love to hear from you! I've been hanging out on Mastodon lately, and I even occasionally check my email!