Managing Dotfiles on Windows 11 With NixOS
I have a confession to make. Until yesterday, I did not have any form of dotfiles management or versioning for my Windows 11 machine. Yes, I, the person who wrote an entire tiling window manager for Windows from scratch in Rust, did not manage my dots.
I had to sheepishly admit this on more than one occasion in the project Discord server when people would watch my live programming videos and then ask if I could share my Windows dotfiles repo.
I became a full time NixOS user earlier this year. I tried running it as my main OS for a few months, but the Linux desktop experience still leaves a lot to be desired, and one of the downsides of writing a tiling window manager that does everything that you want it to do exactly the way you want it to do it, is that going back to other tiling wms that you thought were great before becomes harder.
So I settled on running Windows 11 with
komorebi
as my “desktop environment”
over NixOS running in WSL. It
also doesn’t hurt that I don’t have to jump through any hoops to get Premiere
Pro, Ableton or Elgato software working, or play video games (though
admittedly, the gaming experience on Linux is getting better and better every
day since the release of the Steam Deck.)
Naturally, all my dotfiles are versioned as part of my NixOS configuration flake repo.
While making a tutorial video showing how to set up a remote NixOS server to successfully accept VSCode remote sessions, I had to copy my SSH key from the NixOS VM to Windows. This gave me an idea.
Why not just use NixOS to copy over the dotfiles generated for me by
home-manager
to Windows?
This is what I set about doing.
Let’s take my .gitconfig
as an example. I use home-manager
to generate my
.gitconfig
file rather than writing one by hand, bringing it into the
/nix/store
and then linking it into my $HOME
directory.
{
programs.git = {
enable = true;
package = pkgs.unstable.git;
delta.enable = true;
delta.options = {
line-numbers = true;
side-by-side = true;
navigate = true;
};
userEmail = "sorry robots :(";
userName = "LGUG2Z";
extraConfig = {
url = {
"https://oauth2:${secrets.github.personal_access_token}@github.com" = {
insteadOf = "https://github.com";
};
"https://oauth2:${secrets.gitlab.personal_access_token}@gitlab.com" = {
insteadOf = "https://gitlab.com";
};
};
push = {
default = "current";
autoSetupRemote = true;
};
merge = {
conflictstyle = "diff3";
};
diff = {
colorMoved = "default";
};
};
};
}
There are a couple things to note here. First is that I use
delta
for diffs with some specific
options, and second is that I inject my personal access tokens for GitHub and
GitLab from a git-crypt
ed file into my config for HTTPS clones.
(If you’re interested in different secrets managements strategies, you can check out this dedicated article!)
The output of that Nix expression looks like this:
[core]
pager = "/nix/store/68szslq3sv2gqlc5qzniwh1pf8hn70m2-delta-0.15.1/bin/delta"
[delta]
line-numbers = true
navigate = true
side-by-side = true
[diff]
colorMoved = "default"
[interactive]
diffFilter = "/nix/store/68szslq3sv2gqlc5qzniwh1pf8hn70m2-delta-0.15.1/bin/delta --color-only"
[merge]
conflictstyle = "diff3"
[push]
autoSetupRemote = true
default = "current"
[url "https://oauth2:[email protected]"]
insteadOf = "https://github.com"
[url "https://oauth2:[email protected]"]
insteadOf = "https://gitlab.com"
[user]
email = "sorry robots :("
name = "LGUG2Z"
Setting delta.enable = true;
means that the delta
binary referenced in the
configuration will point to a symlink in /nix/store
. Obviously this won’t
work on Windows, so we’ll have to make a few changes.
I started by writing this shell script package and adding it to systemPackages
.
{...}: let
sync-dots = pkgs.writeShellScriptBin "sync-dots" ''
cp ~/.config/git/config /mnt/c/Users/LGUG2Z/.gitconfig
${pkgs.gnused}/bin/sed -i 's/\/nix\/store\/.*\/bin\///g' /mnt/c/Users/LGUG2Z/.gitconfig
'';
in {
environment.systemPackages = [
sync-dots
]
}
This shell script copies the file symlinked from /nix/store
to its final
location under my $HOME
directory to my $Env:USERPROFILE
in Windows. Once
the file has been copied, we run sed
to remove /nix/store/*/
throughout the
file. This can also be further refined by adding $USERPROFILE
as an
environment variable in WSL using
WSLENV
and referencing that, but it was good enough to test out at this point.
So I ran the command, and it worked as expected. Magic! I then went about
adding the rest of my generated dotfiles that are of use in Windows in this
sync-dots
package, and even moving a few dots that I use only on Windows into
my flake configuration repo to version and copy back in the same way.
It would also be possible to have this script run as a part of
system.userActivationScripts
so that every time the NixOS system is rebuilt for an update, the files are
copied over again to ensure that the latest versions are also on Windows every
time, however for now this is so infrequent that I’d prefer to keep
systemuserActivationScripts
as small as possible and just manually run
sync-dots
whenever I need to.
I am very happy with how this experiment turned out, as I had no appetite to
manage the same dotfiles in two different ways for different operating systems,
and in my experience so far, home-manager
is the golden standard for dotfiles
management.
Now if only there were a nice simple way to cross-compile a bunch of core packages for Windows from NixOS and then copy them over to Windows automatically…
If you have any questions you can reach out to me on Twitter and Mastodon.
If you’re interested in what I read to come up with solutions like this one, you can subscribe to my Software Development RSS feed.
If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.
On 11/14/2023 I was impacted by large scale layoffs at my previous employer. I am currently looking for work. I am an experienced SRE with a strong passion for developer enablement, as well as an accomplished open-source developer with a portfolio of popular Rust and Go projects on GitHub. Please reach out if you are hiring for a role that you think I’d be a good fit for.
If you found this content valuable or if you are a happy user of
komorebi
, please consider sponsoring me
on GitHub or tipping me on
Ko-fi to help me through this uncertain period.