Package management on macOS with nix-darwin | Davis Haupt (2024)

I think Nix is really cool. Nix the packagemanager and functional configuration language is most often associated with NixOS the Linux distro,but nix-darwin makes it almost as easy to declaratively configure macOS as it is to configureNixOS installations. Even if you’ll still relying on Homebrew for package management and never touchnixpkgs, I’d say that Nix with nix-darwin provides the best way to manage packages and systemconfiguration on macOS.

Unfortunately, the resources for getting started and integrating different parts of the Nixecosystem are not particularly approachable for beginners. When I started out I would often useGitHub’s code search to trawl through other people’s configs and try different snippets until Ifound what actually worked. Inspired by Arne Bahlo’s Emacs from Scratchseries, I wanted to create aguide to help folks get started with Nix on macOS from scratch, step by step.

Throughout this series we’ll create a declarative system configuration with Nix where you canmanage anything from your shell aliases to what VSCode extensions you have installed to runningdaemons with launchd. We’ll build up to this incrementally: by the end of this post, you’ll have Nixinstalled on your system and be able to declaratively install system-level packages from eitherNixpkgs or Homebrew.

Installing Nix

I recommend using the DeterminateSystems Nix installer. They have acommand-line utility and also recently cameout with a graphical installer if you’dprefer that.

Setting up nix-darwin

nix-darwin is a Nix library that makes it easy to configuremacOS through Nix.

Nix is a programming language, and Nix configurations are programs. All programs need anentrypoint, we’ll be using a flakeSince this series will focus on system configuration rather than development environments,we’ll only be creating this one flake and won’t cover flakes in-depth. If want to read moreabout flakes, feel free to check out Julia Evans’s blog post onflakes and the Zero to Nix wikipage. to provide the entrypoint to our configuration.

Below is our minimal flake that calls nix-darwin. Make sure to replace $USER with your usernameand $HOSTNAME with your system’s hostname.

You can place this flake in any directory you’d like. For the purposes of this series, we’ll assumethat the flake lives at ~/.config/nix/flake.nix.

# ~/.config/nix/flake.nix{ description = "My system configuration"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nix-darwin = { url = "github:LnL7/nix-darwin"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs@{ self, nix-darwin, nixpkgs }: let configuration = {pkgs, ... }: { services.nix-daemon.enable = true; # Necessary for using flakes on this system. nix.settings.experimental-features = "nix-command flakes"; system.configurationRevision = self.rev or self.dirtyRev or null; # Used for backwards compatibility. please read the changelog # before changing: `darwin-rebuild changelog`. system.stateVersion = 4; # The platform the configuration will be used on. # If you're on an Intel system, replace with "x86_64-darwin" nixpkgs.hostPlatform = "aarch64-darwin"; # Declare the user that will be running `nix-darwin`. users.users.$USER = { name = "$USER"; home = "/Users/$USER"; }; # Create /etc/zshrc that loads the nix-darwin environment. programs.zsh.enable = true; environment.systemPackages = [ ]; }; in { darwinConfigurations."$HOSTNAME" = nix-darwin.lib.darwinSystem { modules = [ configuration ]; }; };}

Activating our nix-darwin config

One of the stranger footguns when using Nix flakes is that all files referenced by a flake must bechecked into source control. This means that you’ll need to have git installed before we set upour Nix environmentIf you’re on a brand new machine, the first time you run git in the terminal you should beprompted to install Xcode Command Line Tools, which includes git.. Files just need to be staged to be accessible, not committed, so git addis sufficient until you want to back up your config to a remote repository.

$ cd ~/.config/nix$ git init$ git add flake.nix

Once all this is set up, we can run nix-darwin to activate our configuration:

$ nix run nix-darwin --extra-experimental-features nix-command --extra-experimental-features flakes -- switch --flake ~/.config/nix

nix-darwin requires sudo, so you’ll be prompted for your password. Nix may error out if thereare files that already exist at paths that it’s trying to replace. Feel free to either rm these ormv them to a backup location, and then re-run the line above.

Once the command succeeds, open up a new terminal window to pick up the new zsh environment andconfirm that darwin-rebuild is installed on your path:

$ darwin-rebuild --helpdarwin-rebuild [--help] {edit | switch | activate | build | check | changelog} [--list-generations] [{--profile-name | -p} name] [--rollback] [{--switch-generation | -G} generation] [--verbose...] [-v...] [-Q] [{--max-jobs | -j} number] [--cores number] [--dry-run] [--keep-going] [-k] [--keep-failed] [-K] [--fallback] [--show-trace] [-I path] [--option name value] [--arg name value] [--argstr name value] [--flake flake] [--update-input input flake] [--impure] [--recreate-lock-file] [--no-update-lock-file] [--refresh] ...

Congrats on setting up nix-darwin! Our configuration is active, but it doesn’t do anything usefulyet. Let’s change that.

Installing your first Nix package

It’s time to install our first package from the nixpkgs repository. Update the list ofsystemPackages declared in flake.nix:

environment.systemPackages = [ pkgs.neofetch pkgs.vim ];

Here, we’re setting the attribute environment.systemPackages to alist. It’s important to point out thatlists in Nix are space-separated rather than comma-separated like most other languages.

pkgs refers to nixpkgs, the standard repository for finding packages to be installed withNixWhile it’s not necessary to fully understand this right now, the configuration value thatwe’re defining is a Nix module. Thenix-darwin.lib.darwinSystem function that’s called at the bottom of the file is responsiblefor passing nixpkgs through to configuration with the name pkgs. We’ll dive deeper intoNix modules in a later post.. Both neofetch and vim are derivations within nixpkgs.

To rebuild our Nix config, we don’t have to use the super long nix run command from above anymoresince nix-darwin added the darwin-rebuild command to our PATH. From now on, we just needto run:

$ darwin-rebuild switch --flake ~/.config/nix

Once this runs successfully, we now have a new command in our PATH:

$ neofetch -L c.' ,xNMM. .OMMMMo lMM" .;loddo:. .olloddol;. cKMMMMMMMMMMNWMMMMMMMMMM0: .KMMMMMMMMMMMMMMMMMMMMMMMWd. XMMMMMMMMMMMMMMMMMMMMMMMX.;MMMMMMMMMMMMMMMMMMMMMMMM::MMMMMMMMMMMMMMMMMMMMMMMM:.MMMMMMMMMMMMMMMMMMMMMMMMX. kMMMMMMMMMMMMMMMMMMMMMMMMWd. 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk 'XMMMMMMMMMMMMMMMMMMMMMMMMK. kMMMMMMMMMMMMMMMMMMMMMMd ;KMMMMMMMWXXWMMMMMMMk. "cooc*" "*coo'"

Now we’re really cooking!

Searching Nixpkgs

Check out nixpkgs search to find other packages you might wantto install.

Installing packages from Homebrew

Nixpkgs is expansive, but some programs are still only available fromHomebrew. nix-darwin provides what I think is the best interface for Homebrewformulae, casks, and even Mac App Store appsWhile we won’t be installing any App Store apps in this post, you can check out thedescription in the nix-darwindocumentation for moreinformation.. Let’s add this right underenvironment.systemPackages:

homebrew = { enable = true; # onActivation.cleanup = "uninstall"; taps = []; brews = [ "cowsay" ]; casks = [];};

Running darwin-rebuild switch --flake ~/.config/nix again will install the Homebrew formulaspecified in the brews list. Try it out:

$ cowsay "homebrew and nix can be best friends" ______________________________________< homebrew and nix can be best friends > -------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||

If you’re on a Mac where you’ve been using Homebrew for a while, you can run brew list and brew list --cask to list your installed formulae and casks. Once you’ve added every package you want to carryover to the corresponding lists in your Nix config, uncomment onActivation.cleanup = "uninstall". Your homebrew config in nix-darwin is now declarative: only the packages specifiedin your flake.nix will be installed, and if you ever remove a package from the lists here it willbe uninstalled the next time you reload with darwin-rebuild switch.

Nixpkgs vs Homebrew

While nix-darwin makes it easy to install packages with Homebrew, I’d recommend trying to find thecorresponding derivations within Nixpkgs when possible rather than relying solely on Homebrewformulae. As the series goes on we’ll see how native Nix derivations are easier work with in Nix.

Additional configuration

You’ve probably gotten tired of entering your password everytime you reload your config. Luckily,there’s a one-liner to enable Touch ID for sudo, which you can put at the end of yourconfiguration:

# ...let configuration = {pkgs, ... }: { # ... security.pam.enableSudoTouchIdAuth = true; };in# ...

All of nix-darwin’s configuration options are worth exploring — we’ll go more in-depth into someof them in future installments of this series, but in case you’re curious, the you can explore allthe configuration options and start making yourconfig your own!

If you’d like to see the full file that we’ve built up over the course of this post, you can find ithere.

Now that we have a handle on our system configuration, in the next post we’ll set uphome-manager and use it manage dotfiles and otherprogram configuration.

Until next time!

Package management on macOS with nix-darwin | Davis Haupt (2024)
Top Articles
Latest Posts
Article information

Author: Rubie Ullrich

Last Updated:

Views: 6300

Rating: 4.1 / 5 (72 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Rubie Ullrich

Birthday: 1998-02-02

Address: 743 Stoltenberg Center, Genovevaville, NJ 59925-3119

Phone: +2202978377583

Job: Administration Engineer

Hobby: Surfing, Sailing, Listening to music, Web surfing, Kitesurfing, Geocaching, Backpacking

Introduction: My name is Rubie Ullrich, I am a enthusiastic, perfect, tender, vivacious, talented, famous, delightful person who loves writing and wants to share my knowledge and understanding with you.