Bootstrapping a multi-host NixOS configuration using flakes and home manager
I’ve fallen in love with the idea of Nix and I have decided to take the plunge. I hate how messy and bit rotten my machines can feel after a few months of use, especially with my addiction to setting up development evironments, so I deliberately never take full system backups (everything important is already in the cloud). The ability to cleanly blitz and start again at any point, knowing exactly what was installed and why, and without having to worry about losing subtle tweaks I’ve made, is very attractive.
My plan is to ditch my mess of a Ubuntu partition and shift my daily driver to NixOS. Then create a similarly configured WSL version for when I need/want to be in Windows.
Browsing various dotfile repos gave me a glimpse into what is possible with a flake+home manager set up, but I found the number of techniques a bit overwhelming. So I tried to condence what I could find into the simplest thing that just works.
This is the flake.nix
I ended up with:
# ~/dotfiles/flake.nix
{
description = "NixOS configuration and home-manager configurations";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { home-manager, nixpkgs, ...}:
{
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/nixos/configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useUserPackages = true;
home-manager.users.return12 = {
imports = [
./home/default.nix
];
};
}
];
};
};
}
Here hosts/nixos/configuration.nix
contains the configuration.nix
created from a fresh install, and home/default.nix
holds the packages
and programs I want available to my user account.
# ~/dotfiles/home/default.nix
{ pkgs, ... }:
{
imports = [
# Individual home manager program configurations, e.g.:
# ./direnv.nix
# ./zsh.nix
# ./starship.nix
# ./neovim.nix
# ./git.nix
# ./taskwarrior.nix
];
home.packages = with pkgs; [
htop
ack
];
home.stateVersion = "22.05";
home.sessionVariables = {
EDITOR = "vim";
};
}
Once this skeleton was in place it was easy enough to add a second
nixConfigurations
entry for the WSL host and start refactoring to extract
commonly used pieces or to separate config when the needs differed. It’s a bit
clunky but I’m hoping the smells I uncover guide me to a much more idiomatic
solution.
These are the steps I followed to get there…
Install NixOS
I decided the that a simple, and non-destructive, first step to bootstrapping my NixOS install would be to install inside a VM. Once happy, I can give up my current Ubuntu partition and install there for real with (hopefully) portable configuration and minimal fuss.
Note: I had to do this under Linux. For some reason booting the NixOS installer failed consistently in Windows
I opted for the graphical installer. The blurb for the download says it will install Plasma but you can opt for no desktop. I decided to go with this because I want to pick my own window manager, etc (I have r/unixporn pretentions).
Test a simple configuration change
Once installed, and rebooted (unmount installation media), and logged in you’ll
be dropped into your barebones instal. Your OS configuration can be found at
/etc/nixos
.
configuration.nix
contains details on system-wide configuration (hostname and
the user account you setup during installation) and installed packages. It
should be pretty empty but contains some hints on how to go about installing
more. hardware-configuration.nix
is automatically generated based on hardward
detected. You can mostly leave it alone.
To prove everything is working as expected make an edit to configuration.nix
sudo nano /etc/nixos/configuration.nix # File is owned by root so you'll need sudo
Find the line that lists the installed system packages and uncomment vim.
environmend.systemPackages = with pkgs; [
vim
];
Once saved you can apply the new configuration.
sudo nix-rebuild switch
You’ll see a bunch of output which will reward you with the ability to launch vim.
You can stop here and build out your system by editing configuration.nix
if
you like.
Enable flake support
Flakes support is still experimental so needs to be explicitly enabled. Add
this to configuration.nix
just after the imports definition.
# Enable nix flakes
nix.package = pkgs.nixFlakes;
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
Then apply with sudo nixos-rebuild switch
as before.
Set up your skeleton in version control
# Create homes for our host and home manager configs
mkdir -p ~/dotfiles/hosts/nixos ~/dotfiles/home
# Copy across the existing, auto-generated configuration so they're under
# version control
cp /etc/nixos/configuration.nix ~/dotfiles/hosts/nixos/
cp /etc/nixos/hardware-configuration ~/dotfiles/hosts/nixos/
# Create your multi-host set up. Use the above example changing user/host name
# when needed
vi ~/dotfiles/flake.nix
# Create your default home manager set up. Home manager itself is installed
# system(s)-wide with flake.nix
# Add separate nix files for whichever programs you want and add them to the
# imports list. I recommend setting up a basic git install so you don't need the
# nix-shell (below) in future.
# See https://github.com/jammus/dotfiles/blob/455f08/home/git.nix for a minimal
# git setup.
vi ~/dotfiles/home/default.nix
# Add to version control. Git is required for flakes to work but as we haven't
# yet installed it for real we need to create a temporary shell with it inside
nix-shell -p git
cd ~/dotfiles
git init && git add .
You can now apply the config from your dotfiles directory with:
nixos-rebuild switch --upgrade --flake '.#' --use-remote-sudo
This will use the nixConfigurations
that matches your current hostname. If you
want to be specific (or your hostname doesn’t currently match what’s defined)
you can use:
nixos-rebuild switch --upgrade --flake '.#hostname' --use-remote-sudo
That’s it!
Commit your changes and push somewhere safe. You can then follow similar steps
when adding a new host to your line up. I’d recommend create a home/gui.nix
file to host specific config (window manager, vscode, etc) for desktop machines
e.g.:
# ~/dotfiles/flake.nix
{
description = "NixOS configuration and home-manager configurations";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { home-manager, nixpkgs, ...}:
{
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/nixos/configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useUserPackages = true;
home-manager.users.return12 = {
imports = [
./home/default.nix
];
};
}
];
};
nixosConfigurations.nixos-dekstop = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/nixos-desktop/configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useUserPackages = true;
home-manager.users.return12 = {
imports = [
./home/default.nix
./home/gui.nix
];
};
}
];
};
};
}
This copy/pasted, one nixosConfigurations
per host method will soon get ugly and is
crying out for a refactor. Once you need to do it, jump in.
Potential errors
Some errors I encountered when test driving the above (mostly from missing steps out):
error: creating symlink
during anixos-rebuild
You need root to apply the system configuration. Prependsudo
or add the--use-remote-sudo
switch.error: getting status of '<path>/flake.nix: No such file or directory
Flakes need to be under versionn control, Make sure you have your repository initialised and have added all files references by your config.error: failed to extract archive (Write failed)
I got this failuire when nix tried to update flake.lock during a rebuild. It seemed to be in an inconsistent state but addingsudo
to the command unstuck me.