Dev Environment Setup With Nix on MacOS
In the last couple of days, I played bit with Nix on MacOS. This is a summary of my current setup.
This article does not go into details about what Nix is or why you would want to use it. For more information on that, read What Is Nix and Why You Should Use It.
Installation
To install Nix on MacOS as a multi-user installation, run:
sh <(curl -L https://nixos.org/nix/install) --daemon
Since /
is read-only since MacOS Catalina, the installation script will create
an APFS volume for the Nix store and mount it at /nix
. See manual.
You may need to source the nix profile at this point.
source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
source /nix/var/nix/profiles/default/etc/profile.d/nix.sh
If you want to configure the operating system via Nix, you can install Nix Darwin. I skipped this for now and only use Nix for installing packages and managing the home directory. To see the options you can set with Nix Darwin, refer to the Nix Darwin manual.
To manage the home directory, I’m using Home Manager. If you are using Nix Darwin, you can install it as a Nix Darwin module. If not, you can use the standalone installation.
Note that the following instructions are for the standalone installation. If you use Nix Darwin, the configuration file will be at a different location and include the configuration specific to Nix Darwin.
Installing Packages
To allow installing unfree software via Nix, create the file ~/.config/nixpkgs/config.nix
with this content:
{
allowUnfree = true;
}
To find a package, run:
nix search kubectl
Add the packages you want to install for your user to
~/.config/nixpkgs/home.nix
. It might look something like this:
{ config, pkgs, lib, ... }:
{
programs.home-manager.enable = true;
home.username = "myusername";
home.homeDirectory = "/Users/myusername";
home.stateVersion = "21.05";
home.packages = [
pkgs._1password
pkgs.awscli
pkgs.circleci-cli
pkgs.fish
pkgs.git
pkgs.google-cloud-sdk
pkgs.graphviz
pkgs.htop
pkgs.kubectl
pkgs.kubernetes-helm
pkgs.kubetail
pkgs.hugo
pkgs.jq
pkgs.minikube
pkgs.nodejs-12_x
pkgs.plantuml
pkgs.python3
pkgs.tasksh
pkgs.taskwarrior
pkgs.terraform
pkgs.tldr
pkgs.tree
pkgs.watson
pkgs.yarn
pkgs.yq
];
}
After changing the configuration, run:
home-manager switch
Git
You can use Home Manager to configure Git by adding something like this to
.config/nixpkgs/home.nix
:
{ config, pkgs, lib, ... }:
{
# ...
programs.git = {
enable = true;
userEmail = "email@example.com";
userName = "Mrs. Developer";
signing.key = "1234ABCD";
signing.signByDefault = true;
ignores = [ "*~" ".DS_Store" ];
extraConfig = {
core = {
editor = "nano";
};
url = {
"git@github.com:" = {
insteadOf = "https://github.com/";
};
};
pull = {
rebase = true;
};
};
};
}
Refer to the manual for all available options.
Note: If there is already a git configuration file, you need to remove or rename it before the new configuration can be applied. This holds true for any other configuration files as well.
Environment Variables
You can set environment variables with home.sessionVariables
:
{ config, pkgs, lib, ... }:
{
# ...
home.sessionVariables = {
EDITOR = "nano";
};
}
Fish
Configure Fish with Home Manager by adding this to
~/.config/nixpkgs/home.nix
:
{ config, pkgs, lib, ... }:
{
# ...
programs.fish = {
enable = true;
};
}
Oh My Fish Plugins
You can add any Oh My Fish plugin
without actually installing Oh My Fish by adding it to programs.fish.plugins
.
{ config, pkgs, lib, ... }:
{
# ...
programs.fish = {
enable = true;
plugins = [
{
name = "bass";
src = pkgs.fetchFromGitHub {
owner = "edc";
repo = "bass";
rev = "50eba266b0d8a952c7230fca1114cbc9fbbdfbd4";
sha256 = "0ppmajynpb9l58xbrcnbp41b66g7p0c9l2nlsvyjwk6d16g4p4gy";
};
}
{
name = "foreign-env";
src = pkgs.fetchFromGitHub {
owner = "oh-my-fish";
repo = "plugin-foreign-env";
rev = "dddd9213272a0ab848d474d0cbde12ad034e65bc";
sha256 = "00xqlyl3lffc5l0viin1nyp819wf81fncqyz87jx8ljjdhilmgbs";
};
}
]
};
}
The options for pkgs.fetchFromGithub
are:
owner
: The Github repo owner name.repo
: Guess what.rev
: The commit hash or tag.sha256
: The hash of the extracted directory.
To find the correct value for sha256
:
- Set
sha256 = lib.fakeSha256
. - Run
home-manager switch
.
In the output, you should see something similar to:
hash mismatch in fixed-output derivation '/nix/store/74010535gk31hpxnmsbwda8dgz2i8ajq-source':
wanted: sha256:0000000000000000000000000000000000000000000000000000
got: sha256:00xqlyl3lffc5l0viin1nyp819wf81fncqyz87jx8ljjdhilmgbs
You can now copy that sha256 hash.
Oh My Fish Themes
You can install Oh My Fish themes just the same way:
programs.fish = {
# ...
plugins = [
# ...
{
name = "bobthefish";
src = pkgs.fetchFromGitHub {
owner = "oh-my-fish";
repo = "theme-bobthefish";
rev = "a2ad38aa051aaed25ae3bd6129986e7f27d42d7b";
sha256 = "1fssb5bqd2d7856gsylf93d28n3rw4rlqkhbg120j5ng27c7v7lq";
};
}
]
};
If you have a look at .config/fish/conf.d/plugin-bobthefish.fish
(or any
other plugin file in the same folder), you will see that by default, only
$plugin_dir/conf.d/*.fish
, $plugin_dir/key_bindings.fish
and
$plugin_dir/init.fish
are sourced. However, Oh My Fish themes have all
the necessary fish files in the root directory of the plugin. To actually
activate the theme, you need to append the plugin file like this:
{ config, pkgs, lib, ... }:
{
# ...
programs.fish = {
# ...
};
xdg.configFile."fish/conf.d/plugin-bobthefish.fish".text = lib.mkAfter ''
for f in $plugin_dir/*.fish
source $f
end
'';
}
After you run home-manager switch
, .config/fish/conf.d/plugin-bobthefish.fish
will be updated and the theme will be activated when you open a new shell.
Sourcing the Nix profile
In the current state, the nix profile is not sourced automatically. To change that, add this:
programs.fish = {
# ...
loginShellInit = ''
if test -e /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
fenv source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
end
if test -e /nix/var/nix/profiles/default/etc/profile.d/nix.sh
fenv source /nix/var/nix/profiles/default/etc/profile.d/nix.sh
end
'';
};
Note that this code makes use of the foreign-env
plugin installed above.
Set up a config repository
I think it is a good idea to put your configuration into a Git repository. One way to do that is described in the tutorial The best way to store your dotfiles: A bare Git repository.
Basically you initialize a bare git repository and define an alias to make working with this repo easier.
git init --bare $HOME/.cfg
alias config='git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
config config --local status.showUntrackedFiles no
You can now commit the config that we have so far with:
config add ~/.config/nixpkgs
config commit -m "add nix configuration"
To make the alias always available in Fish, add it to
.config/nixpkgs/home.nix
:
programs.fish = {
# ...
shellAliases = {
config = "git --git-dir=$HOME/.cfg/ --work-tree=$HOME";
};
};
You can set up a new machine like this:
git clone --bare <git-repo-url> $HOME/.cfg
alias config='git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
config config --local status.showUntrackedFiles no
config checkout
Outside of Nix
Brew
You can now install nearly all the packages you need with Nix instead of
Homebrew, but if you want to install GUI tools via the
command line, you will still need to use
Homebrew Cask. To make it easier
to sync the casks across machines, you can create a Brewfile
and add it to
the config repo.
If you already have casks installed and want to create a Brewfile
for the
first time, run:
brew bundle dump
The result will look something like this:
tap "homebrew/bundle"
tap "homebrew/cask"
tap "homebrew/core"
tap "homebrew/services"
cask "docker"
cask "firefox"
cask "flux"
cask "iterm2"
cask "karabiner-elements"
cask "keybase"
cask "sublime-merge"
cask "sublime-text"
cask "typora"
You can add this file to the repo. To install new packages after editing the file, run:
brew bundle install
To uninstall packages after removing them from the file, run:
brew bundle cleanup -f
You can optionally install mas to manage software that is installed via the App Store in the Brewfile as well (see Homebrew Bundle readme).
iTerm2
iTerm2 has an option to load the preferences from a
custom location
(iTerm2 → Preferences → General → Preferences → Load preferences from a custom folder or URL
).
You can set the location to ~/.config/iterm2
, click Save Current Settings to Folder
and add the config to the repo.
Sublime Text 3
You can copy the User folder of Sublime Text 3 to the config folder, delete the original directory and create a symlink.
mkdir ~/.config/sublime-text-3
cp ~/Library/Application\ Support/Sublime\ Text\ 3/Packages/User ~/.config/sublime-text-3
rm -rf ~/Library/Application\ Support/Sublime\ Text\ 3/Packages/User
ln -s ~/.config/sublime-text-3/User/ ~/Library/Application\ Support/Sublime\ Text\ 3/Packages/User
Add a .gitignore
file:
echo ".SublimeREPLHistory
oscrypto-ca-bundle.crt
Package Control.ca-bundle
Package Control.ca-certs/
Package Control.ca-list
Package Control.cache/
Package Control.last-run
Package Control.merged-ca-bundle
Package Control.system-ca-bundle
Package Control.user-ca-bundle" >> ~/.config/sublime-text-3/User/.gitignore
And commit the folder to the repo.
Nix Shell
Instead of making packages available globally, you can add a file called
shell.nix
in your projects, which may look something like this:
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
mkShell {
buildInputs = [
elixir_1_10
nodejs-12_x
];
}
You can get into a shell with these packages available by running:
nix-shell
Direnv
You can use direnv to automatically load the environment when changing into a project folder.
Add this to ~/.config/nixpkgs/home.nix
:
{ config, pkgs, lib, ... }:
{
# ...
programs.direnv.enable = true;
programs.direnv.enableNixDirenvIntegration = true;
programs.fish = {
# ...
loginShellInit = ''
# ...
eval (direnv hook fish)
'';
};
}
Don’t forget to run home-manager switch
.
Create a file called .envrc
in your project directory.
echo use_nix > .envrc
Then run:
direnv allow .
From now on, the environment will be automatically loaded whenever you cd
into
the directory and unloaded when you leave it.
Next
I haven’t looked into handling services like databases or message brokers in a Nix shell environment yet. One solution for running a local PostgreSQL database is described in the article Using Nix in Elixir projects.
If you spotted an error, need a random compliment or have any other remarks, don’t hesitate to drop a message.