A couple of months ago, I started using Nix on the Mac instead of homebrew. This included setting up home-manager and nix-darwin. There are a lot of setup guides on these and I’m not going to repeat that work. I will mention some pieces that I found particularly annoying and their solutions.
Multiple Repositories
Nix allows you to specify multiple repositories where it can fetch code. However, getting this to work with home-manager can be annoyingly difficult to figure out, especially if you use a flake with multiple files like myself. This is how I got it to work:
flake.nix
:
{
description = "Darwin system flake";
inputs = {
nixpkgs-unstable.url = "github:NixOs/nixpkgs/nixpkgs-unstable";
nixpkgs-stable.url = "github:NixOs/nixpkgs/nixpkgs-24.05-darwin";
# nix-darwin, home-manager, etc are here
};
outputs = inputs@{ self, nix-darwin, nixpkgs, nixpkgs-unstable, home-manager, ... }:
let
nixpkgsConfig = {
config.allowUnfree = true;
};
in {
darwinConfigurations = let
inherit (inputs.nix-darwin.lib) darwinSystem;
inherit (inputs.nix-homebrew.darwinModules) nix-homebrew;
inherit (inputs.home-manager.darwinModules) home-manager;
in {
SystemName = darwinSystem rec {
system = "aarch64-darwin";
specialArgs = { inherit inputs; };
modules = [
nix-homebrew
./hosts/systemName/configuration.nix
home-manager
{
nixpkgs = nixpkgsConfig;
home-manager.users.userName = {
imports = [./home/home.nix];
_module.args.unstable = nixpkgs-unstable.legacyPackages.${system};
};
}
];
};
};
};
}
(with SystemName
, systemName
, and userName
values substituted to their appropriate values)
home/home.nix
:
{ pkgs, unstable, ... }:
{
nixpkgs.config.allowUnfreePredicate = (pkg: true);
home.stateVersion = "24.05";
imports = [
./zsh.nix
];
home.packages = with pkgs; let
myffmpeg = (ffmpeg.override {
withVdpau = false; # Attempts to initialize vdpau starts X11
});
in with pkgs; [
(unstable.mpv-unwrapped.override {
ffmpeg = myffmpeg;
})
unstable.yt-dlp
(python3.withPackages
(ps: with ps; [
pyyaml
])
)
];
}
I’ve omitted several packages that I use to just keep some examples here. I’m using the unstable version of MPV and yt-dlp because I care more about them being up to date but I use the stable version of other packages.
Customizations
From the above examples, you can see that I customized how FFmpeg is built. By default, FFmpeg is built on MacOS with vdpau, which needs to initialize X-windows to detect devices. These don’t work on the Mac but that doesn’t stop it from starting up Xquartz randomly. So above, I build FFmpeg without vdpau, and then I use my customized FFmpeg in MPV.
Launch Services
One of my installed packages is MPV which is built as an application. Nix-darwin will created a symlink inside ~/Applications
pointing to a directory containing links to these applications and will register them with launch services. However, this becomes an issue when you update versions as launch services will continue to use the old apps as long as they continue to exist on the disk. Nix will preserve these old versions for a period of time before it cleans them up. So this means opening files or launching the app will not use the latest version of the app for quite some time. Not much point in upgrading apps if you can’t use the upgraded version any time soon.
I tried a few solutions to this problem. One was to rebuild the launch services database via: /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain user -domain local -domain system
. This had the interesting side-effect that it completely breaks System Settings. Going into System Settings and clicking on one of the other panes (such as Sound
) will cause it to immediately jump back to General
. If you then re-launch System Settings, you will then be missing nearly every pane except for General
and one other I’m not remembering right now. Clearly this is not a good solution.
I then settled on a script to fix this problem:
redoNixRegistrations.sh
:
#! /usr/bin/env bash
declare -A linked
for i in ~/Applications/Home\ Manager\ Apps/*; do
dest="$(readlink "$i")"
linked["$dest"]="1"
done
existingPaths="$(/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump | grep -e "path: */nix/" | sed -E 's/.*(\/nix\/.*) \(0x[0-9a-f]+\)/\1/')"
unregistered="0"
IFS=$'\n'; for i in $existingPaths; do
if [ "${linked["$i"]}" != "1" ]; then
echo "Unregistering $i"
"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister" -u "$i"
unregistered="1"
fi
done
if [ "$unregistered" == "1" ]; then
defaults write com.apple.dock ResetLaunchPad -bool true; killall Dock
darwin-rebuild switch --flake ~/git/scriptsAndConfig/config/nix-darwin
fi
The script works by:
- Examining the links in
~/Applications/Home Manager Apps
and storing their destinations in a variable. - Looking at the launch services database and examines all entries it has in there which are in the
/nix
directory. - Remove any entries in the launch services database (one at a time) that is no longer linked in
~/Applications/Home Manager Apps
. - If it removed any, it resets Launch Pad and has darwin-nix re-register it’s apps
I’m not sure why I have to have nix-darwin re-register its apps as the newest versions were in the launch services database both before and after I run this script, but without this step they never showed in up launch pad. That aside, this seems to be working for me in that launching apps will always be the most recent version that nix installed without breaking System Settings.