My Nix Powered WSL2 Workflow
Date:
My job requires that I use a government supplied Windows machine. I'm a staunch GNU/Linux user by nature. Thankfully, the government that I work for gives its developers admin power over their machine. With Windows, the best compromise means using native Windows applications for all the “tricky” stuff (Teams, VPN, Outlook, etc.) and WSL2 for everything else.
WSL2 is surprisingly well fleshed out in Windows. The only real issue I have is that it uses a utility called WSLg, which is a Wayland first display system. Because Wayland and WSL2 both still have their quirks, graphical programs don't always work or display perfectly well. WSLg also seems crash and stop working after a few hours of running WSL2, especially if the machine is allowed to sleep. When I stick to the TTY, I can work all day unimpeded.
For my GNU/Linux distribution I choose NixOS. It has a large selection of packages and a highly reproducible build system. I recently had to upgrade to Windows 11, which required formatting my work machine. Thanks to NixOS, I was productive in about 20 minutes. All I had to do was install the WSL2 image, restore my Nix configuration from backup and re-build my system. You can find the WSL image for NixOS here: https://github.com/nix-community/NixOS-WSL
Wezterm
Because I stick to the TTY, and PowerShell is kind of its own thing, I need a good native terminal emulator in Windows. We have a few good choices. I narrowed them down to three: Terminal, WezTerm and Alacritty. Any one of these works nicely. I prefer WezTerm because it comes fully featured and is configurable with Lua. I've configured it to launch my NixOS instance right away when I open it. Note the default_prog
key in the below example.
local wezterm = require 'wezterm' return { font = wezterm.font 'JetBrainsMono Nerd Font Mono', -- font installed separately font_size = 10, color_scheme = 'Material Darker (base16)', colors = { background = "#111111" }, hide_tab_bar_if_only_one_tab = true, window_padding = { left = 0, right = 0, top = 0, bottom = 0 }, default_prog = { 'wsl', '~' }, initial_rows = 50, initial_cols = 150, keys = { { -- Let Emacs do ALT+Enter in the TUI key = 'Enter', mods = 'ALT', action = wezterm.action.DisableDefaultAssignment } } }
Nix system configuration
I need some special configurations to get Docker and Syncthing working. Docker needs no explanation. Syncthing is an indispensable tool for synchronizing data across my devices on my LAN. I also want my user to be the default WSL user when I log into the shell session.
{ lib, pkgs, config, modulesPath, ... }: with lib; { imports = [ <nixos-wsl/modules> ]; i18n.defaultLocale = "en_US.UTF-8"; time.timeZone = "America/Vancouver"; environment = { systemPackages = with pkgs; [ docker-compose emacs pinentry ]; pathsToLink = ["/share/bash-completion"]; }; programs.zsh.enable = true; virtualisation.docker.enable = true; users.users.trev = { name = "trev"; isNormalUser = true; home = "/home/trev"; description = "Trevor Richards"; extraGroups = [ "docker" "wheel" ]; shell = pkgs.bash; }; services.syncthing = { enable = true; user = "trev"; dataDir = "/home/trev/Sync"; configDir = "/home/trev/.config/syncthing"; }; wsl = { enable = true; wslConf.automount.root = "/mnt"; defaultUser = "trev"; startMenuLaunchers = true; }; # Enable nix flakes nix.extraOptions = '' experimental-features = nix-command flakes ''; system.stateVersion = "22.05"; }
Home manager configuration
I use a Nix home-manager
setup to build my user packages and shell environment. I sign my commits with PGP and access git remotes via SSH with my PGP authorization key. These functionalities rely on a display worthy pinentry program. The one that consistently works for me is pinentry-qt
. Nix's home-manager gets this done for me with just a few configuration lines.
programs.git = { enable = true; userName = "Trevor Richards"; userEmail = "trev@trevdev.ca"; signing = { key = "0FB7D06B4A2AF07EAD5B1169183B63068AA1D206"; signByDefault = true; }; extraConfig = { init = { defaultBranch = "main"; }; core = { excludesFile = "~/.gitignore"; }; }; }; programs.gpg = { enable = true; }; services.gpg-agent = { pinentryPackage = pkgs.pinentry-qt; enable = true; enableSshSupport = true; defaultCacheTtl = 60480000; # I prefer not to type my password more than once defaultCacheTtlSsh = 60480000; maxCacheTtl = 60480000; sshKeys = ["FF9F589746CBDCE989E5C2D75928BCCDC1E7C015"]; };
I use bash for my shell. To get the gpg-agent working properly, I add a small hack to the initExtra
configuration option for programs.bash
.
programs.bash = { initExtra = '' export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket); ''; };
That's it for now
For a more exhaustive look at my Nix WSL2 config, you may find it here: https://codeberg.org/trevdev/nix-wsl2
This setup more or less just works day-to-day without any tweaks. It stays out of my way so I can worry about work. Feel free to email me or message me on Mastodon if you have any cool WSL2 hacks of your own.
At some point in the future, I will publish the WSL2 specifics of my Emacs configuration. Be sure to visit me later :)