Nix Binary Cache for MacOS/Nix-Darwin with Attic

Greetings!

I couldn’t find any MacOS examples for a Nix binary cache, so I wrote a module for it using attic and am running it on my M1 Mac Mini.

I’m using flakes, so I’m going to assume you have an input like so:

inputs.attic.url = "github:zhaofengli/attic";

And then here’s my module which runs on my M1 Mac Mini (and therefore populates the cache with MacOS binaries):

{ config, pkgs, lib, attic, ... }:

let
  system = pkgs.system;
  atticServer = attic.packages.${system}.attic-server;
  atticClient = attic.packages.${system}.attic-client;
  garbageCollectCache = pkgs.writeShellScriptBin "nix-darwin-cache-garbage-collect" ''
    ${atticServer}/bin/atticd --mode garbage-collector-once &>>/tmp/binary-cache.log
  '';
  populateCache = pkgs.writeShellScriptBin "nix-darwin-cache-populate" ''
    rm -rf /tmp/nixos-configs
    ${pkgs.git}/bin/git clone https://github.com/heywoodlh/nixos-configs /tmp/nixos-configs
    cd /tmp/nixos-configs
    # aarch64 build
    ${pkgs.nix}/bin/nix build .#darwinConfigurations.mac-mini.config.system.build.toplevel
    ${atticClient}/bin/attic push nix-darwin ./result

    rm -rf /tmp/nixos-configs
  '';
  runCache = pkgs.writeShellScript "serve-cache" ''
    ${atticServer}/bin/atticd --listen 0.0.0.0:8080 &>>/tmp/binary-cache.log
  '';
in {
  launchd.daemons.cache-populate = {
    command = "${populateCache}/bin/nix-darwin-cache-populate";
    serviceConfig.StartInterval = 86400; # run once a day
  };

  launchd.daemons.cache-garbage-collect = {
    command = "${populateCache}/bin/nix-darwin-cache-garbage-collect";
    serviceConfig.StartInterval = 604800; # run once a week
  };

  launchd.daemons.nix-cache = {
    command = "${runCache}";
    serviceConfig.RunAtLoad = true;
    serviceConfig.KeepAlive = true;
  };

  environment.systemPackages = [
    garbageCollectCache
    populateCache
  ];
}

This builds my nix-darwin flake at this URI:

github:heywoodlh/nixos-configs#mac-mini

and loads them into the cache. Obviously, this is specific to my setup, so you’ll likely want to change the things you build in the populateCache attribute.

Caveat: on your machine running this module, you need to login to your attic instance with the attic client (you can get the command to login from /tmp/binary-cache.log after this module is loaded):

attic login local http://localhost:8080 ...

Then, create the nix-darwin cache like this:

attic cache create nix-darwin
attic cache configure nix-darwin --public
attic cache configure nix-darwin --retention-period '7d'

Then populate the cache by running the nix-darwin-cache-populate script provided in the populateCache attribute.

Check the availability for your cache like so:

curl http://127.0.0.1:8080/nix-darwin/nix-cache-info

Obviously, this example specifically targets Nix-Darwin stuff in my setup, but could be extended to any other thing you can build with Nix. You could also easily extend this into building Linux things on MacOS with the linux-builder provided with Nix-Darwin and load them into attic. Alternatively, you could have a MacOS remote builder and push the build result into a different NixOS machine running attic. The options are endless with Nix!

I would be very interested in other MacOS-specific binary cache implementations because I couldn’t find any. Should probably contribute an attic module to Nix-Darwin, but don’t yet have willpower for it. :smile:

Here’s my binary-cache.nix at the time of writing this, will likely evolve as time goes on: https://github.com/heywoodlh/nixos-configs/blob/abcc5d8a61f51304426df88ac8c6e63440036db7/darwin/roles/binary-cache.nix

edit: added the commands to create the nix-darwin cache after attic is running

4 Likes