Nix on WSL
nix wslI constantly hop to three hetrogenous development environments: macOS for daily
drive; Ubuntu on WSL for side projects, including this blog. Unlike the
production environment, — a VPS server orchestrated by saltstack; the
personal development environments are loosely managed. I installed packages in
an ad-hoc fashion, and managed the dotfiles with vcsh
.
I considered to adopt some configuration orchestation for easy replication.
Maybe not saltstack
, as it feels rather heavyweight. I also felt a little
overwhelmed by the cognitive overhead, cast by the plugin manager, such as
vim-plug, prezto, and tpm; and version managers, such as pyenv, rbenv,
and nvm.
Introduction to Nix
My colleague, Matt introduced me to the Nix, a tool to make
reproducible, declarative and reliable systems
Nix takes a unique approach for package management: it abandoned the Filesystem
Hierarchy Standard (FHS) maintained by the Linux Foundation. The packages are
stored in the nix/store
, such as
/nix/store/176bs3fwqhhvbha5ddvfkzz85aq5qvkm-libevent-2.1.12
. The shared
objects, aka .so
files, are NOT copied to the RPATH
. They are linked
directly from the store, and executables are symbol linked to the shell search
path. Using tmux
as a concrete example:
❯ ldd ~/.nix-profile/bin/tmux
linux-vdso.so.1 (0x00007ffec29f8000)
libutil.so.1 => /nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib/libutil.so.1 (0x00007f82535bc000)
libncursesw.so.6 => /nix/store/j2y9dgxlp4g5vfqcsylzdlci3bygrmlv-ncurses-6.2/lib/libncursesw.so.6 (0x00007f8253548000)
libevent-2.1.so.7 => /nix/store/176bs3fwqhhvbha5ddvfkzz85aq5qvkm-libevent-2.1.12/lib/libevent-2.1.so.7 (0x00007f82534ee000)
libresolv.so.2 => /nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib/libresolv.so.2 (0x00007f82534d5000)
libc.so.6 => /nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib/libc.so.6 (0x00007f8253314000)
libpthread.so.0 => /nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib/libpthread.so.0 (0x00007f82532f1000)
/nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib/ld-linux-x86-64.so.2 => /nix/store/a3syww9igm49zdzq3ibzw9m8ccvsgxla-glibc-2.32/lib64/ld-linux-x86-64.so.2 (0x00007f82535c3000)
This approach allows multiple-versioned shared objects stored in the system,
since they no longer compete the same slot in the /usr/lib
. We can install
multiple-versioned node
, but only one can be symbol linked in the shell search
path. Nix has nix-shell
to cope with this limitation: it cherry pick artifacts
to represent the system state in a different perspective.
NixOS and home manager leverage the Nix expression for declarative system
states, such as services, and dotfiles. We will revisit the NixOps
when
migrating the VPS server to NixOS.
Nix on WSL
Assume we use the Debain as our base system, first install the required packages
to bootstrap the Nixpkgs
.
sudo apt-get update
sudo apt-get install curl xz-utils ca-certificates
Install the nixpkgs
, and source the nix.sh
to enter the promised land.
curl -L https://nixos.org/nix/install | sh
source $HOME/.nix-profile/etc/profile.d/nix.sh
Note we do not install the full-fledged NixOS in WSL as the the systemd in WSL still lack some key features. I also prefer managing the dependent services via docker containers.
Subscribe the home-manager
channel, and install the home-manager
.
nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
nix-channel --update
nix-shell '<home-manager>' -A install
Then we can edit $HOME/.config/nixpkgs/home.nix
, and run home-manager switch
to apply the change. Here is one simple example:
{ config, pkgs, ... }:
let
locale = "C.UTF-8";
homedir = builtins.getEnv "HOME";
username = builtins.getEnv "USER";
in
{
home = {
packages = with pkgs; [
htop
ripgrep
];
sessionVariables = {
LANG = locale;
LC_ALL = locale;
TMUX_TMPDIR = "/tmp";
};
username = username;
homeDirectory = homedir;
stateVersion = "21.03";
};
programs.home-manager.enable = true;
# tmux
programs.tmux = {
enable = true;
terminal = "screen-256color";
shortcut = "a";
baseIndex = 1;
keyMode = "vi";
sensibleOnTop = true;
plugins = with pkgs; [
tmuxPlugins.pain-control
tmuxPlugins.copycat
tmuxPlugins.yank
tmuxPlugins.logging
];
}
}
In this code snippet:
- We configure the
username
, andhomeDirectory
from the environment variables,USER
, andHOME
respectively. - Then define the environment variables, such as
TMUX_TMPDIR
. - We enable, aka install, the
tmux
, and configure the tmux’s key binding, prefix, and plugins. See the similar .tmux.conf here. Thanks to thehome-manager
’s contributor, we can manage tmux without wading through the tmux configurations.
You may checkout my nix-config here.
Troubleshooting
You may encounter the following permission error as being written:
nix-shell '<home-manager>' -A install
...
error: moving build output '/nix/store/nnj0sc87fycmcv97inqvdzl1ghr1gwkp-nmd' from the sandbox to the Nix store: Permission denied
This is due to the Nix#4295, you can
workaround this by setting sandbox = false
in /etc/nix/nix.conf
.
You may also encounter the error that tmux
complains that the path
/run/user/1000
is not found during launch. This is caused by the default
secureSocket
option, see
here.
We can explicitly setup TMUX_TMPDIR
for single-user mode.
On-demand environment
With nix-shell
and direnv
, we can jump to a dedicated working environment
when switch the current work directory. First, let’s enable the direnv
in the
home.nix
:
# Use nix-direnv integration
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
Then in the root of the project folder, create a shell.nix
such as:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
buildInputs = with pkgs; [
nodejs-14_x
(yarn.override { nodejs = nodejs-14_x; })
];
}
This would create a nix-shell with latest nodejs-14.x
with yarn installed.
Closing thoughts
Nix and home manage provides a cohesive abstraction for package and configuration management. It was quite delightful to work with Nix expressions as a novice. I would continue explore the Nix ecosystem, stay tuned.