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-crypted 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.