Nix in VCS with dotfiles? (also OhMyZsh help)

I went through the guides but couldn’t find one that mapped 1:1 to what I’m trying to do.

I’m new to NixOS, installed it in a laptop, and already messed up as I started modifying the config directly and lost the original 1st generation config, later came to know about flakes and home manager and I think I have a general idea about how to do version control now.

If I get it right the ideal way to handle it is to extend the config with Flakes that you can organize and position somewhere else that is easier to do version control, the most common being dotfiles repo in GitHub.

Home manager may be useful specifically because the syntax looks cleaner and easier to read, but configs that are commonly saved in dotfiles are better handled outside, the reason is that you own the files rather than having a symlink to a file owned by root and need sudo access to modify them (case in point the .zshrc we use to configure the Zsh and OhMyZsh packages).

The other reason is that dotfiles are configs for programs, there’s no reason to rebuild when the config changes, in the case of Zsh just reload (re-source maybe) the config (usually omz reload), so ideally I would just handle all dotfiles with GitHub and GNU Stow.

The 2 issue I’m facing at this point are:

  • How to set up version control for the Nix config, flakes and home manager config files (and how that ties up to a new install of NixOS, do I just clone the repo and in the first iteration tell the config to somehow load the flakes and other files?).
  • How to set up OhMyZsh, I have installed (or configured) the pkgs.zsh and pkgs.ohMyZsh, but since the path where the latter is installed, I don’t know where to source the latter from the .zshrc file, I know it get’s installed in the nix store, but as this is configuration only, I want to avoid a very specific path to the OhMyZsh, it should work even If I update the versions of Zsh and OMZ.

The config looks like this:

nix.settings.experimental-features = [ "nix-command" "flakes"];

environment.systemPackages = with pkgs; [
  pkgs.git
  ...
  pkgs.oh-my-zsh
  pkgs.zsh
  vim
  wget
];

And my .zshrc looks like this, the important part that is not working at the moment is the source of OMZ, I guess I would have to add a very specific case for NixOS?:

# If you come from bash you might have to change your $PATH.
# export PATH=$HOME/bin:/usr/local/bin:$PATH

# Path to your oh-my-zsh installation.
case $(uname) in
Darwin)
  export ZSH="/Users/carlos/.oh-my-zsh"
  ;;
Linux)
  export ZSH="$HOME/.oh-my-zsh"
  ;;
esac
# Set name of the theme to load --- if set to "random", it will
# load a random theme each time oh-my-zsh is loaded, in which case,
# to know which specific one was loaded, run: echo $RANDOM_THEME
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
ZSH_THEME="robbyrussell"

...

source $ZSH/oh-my-zsh.sh

Actually, flakes are unnecessary for this, you can use nix and change the configuration entrypoint file with NIX_PATH='nixos-config:<path>'.

This can be made permanent in your NixOS config with nix.nixPath. Personally I would recommend this over flakes for newcomers.

GitHub is completely optional as far as VC goes, it’s just a platform for sharing and storing git repositories online. The term for this is a “git forge”. Codeberg is a less enshittified forge if this is what you want, but neither is relevant to VC.

The actual VC tool is git, and it works completely without third party services. It’s very worth learning how to use git without the third party services, especially if you’re going to be involved in the nix community.

Very notably, git != GitHub - the latter isn’t related to git at all, despite the name, it’s a git-compatible Microsoft product, while git is a FOSS tool initially written by Linus Torvalds himself.

The first few pages of the git book should be mandatory reading for any programming-related course, honestly. Hell, it - and using git - should be part of school curriculums.

Right, here you seem to have some fundamental misconceptions. I understand what you are saying, however:

  • Even if you use home-manager, you do not need to use nix to describe your config files; you can simply symlink raw files into place with the xdg.configFile.source option & co.
  • You cannot modify the files behind the symlinks home-manager creates; in addition to being owned by root, the filesystem they live in is read-only, so not even root can edit them.
  • You do not need to edit the files as root, editing the files wherever home-manager gets them as your user and rebuilding is sufficient.

Right, I appreciate and understand that perspective. I felt similar back when I started out, though I got here out of frustration with stow’s limitations. Took me a little to come around to the way home-manager does things, but I now believe it to be inherently better.

The perspective of a NixOS/home-manager user is that configuration is no less complex than actually built software. After all, it changes software behavior, so tracking it and having tooling to help you roll back accidents (like making zsh no longer work because of a syntax error in your .zshrc) is important.

Furthermore, often you need to replicate snippets shared between different config files, depend on details of software you use elsewhere, and generally do all kinds of little “glue” things. Building your configuration with nix, even if the actual build is quite trivial, can be very powerful because of all of these details.

The trade-off is that you need to rebuild, so your changes aren’t instantaneous, yes, but they almost never are anyway - as you say, for zsh you need to reload your zshrc to get the result, for example.

home-manager offers functionality to automatically run that reload, making a config update a simple matter of a rebuild switch to update all your applications, no matter what you changed; this takes some configuration for some applications, but generally it’s less big of a deal than it seems at first glance.

I’m not just saying this to be opinionated, but because:

You can’t, or well, there’s no built-in way to do that, because you’re trying to use all of this in a way that it is not designed to be used.

With some effort, you can set up your own systemd unit or activation script that runs stow or such, but there’s no off-the-shelf command or config option.

You need to use nix to resolve the package to an actual string. home.packages will put binaries in $PATH (by using nix to resolve the string), but oh-my-zsh is not a binary.

You have to use nix to build an environment variable, configuration file, something to get a handle on it, or simply fall back to the home-manager options.

One way would be e.g. to:

{ pkgs, ... }: {
  xdg.configFile."zsh/ohmyzsh-source.zsh".text = ''
    source ${pkgs.oh-my-zsh}share/oh-my-zsh/<whatever-the-entrypoint-is>
  '';
}

And to source ~/.config/zsh/ohmyzsh-source.zsh in your zshrc.

You should definitely remove it from home.packages too.

3 Likes

Thanks for the extensive response, NixOS has some learning curve and I’m just struggling to get started.

It’s late now so I’ll address the rest tomorrow but these 2 I can tell at a glance:

  1. I deal with GitHub so much that I tend to forget it only shares half the name with Git, yes it is Git that we use to do VC and GitHub is just a glorified NAS, but the point got across (although I can’t remember the last time I used Git without a GUI, I use GitKraken so much now that I probably forgot the CLI commands by now).
  2. I probably let myself be influenced by YouTube people on their rant on HomeManager, maybe it is better for me just because the guy that ditched it is a different kind of developer, he does a lot of terminal plugin stuff while I mostly do web apps & servers, still the part that I liked about it is that with Stow and the dotfiles most of my configs would be mostly OS agnostic, would a nix/HomeManager config work outside of NixOS? (I know there’s Nix as a package manager, just not sure how compatible it is).

In case you want to see the video, here’s the link: https://www.youtube.com/watch?v=U6reJVR3FfA

I use my home manager configuration on both mac, arch and nixos. There is no requirement to use nixos to use home-manager.

I don’t know about the rest of that guys content, but repeatedly characterising using home-manager like being in an abusive relationship is weird. Like everything in life, you make trade-offs. Home manager trades freely editing your files in exchange for not manually tracking the relationships between your files and your packages. That’s a trade off I’ll make every day of the week because unlike this guy I guess I use my computer to get things done, not to endlessly tweak it so that I can make content about configuring my computer. Do not be fooled by statements like “oh, because I do everything in the terminal home-manager is bad”. That’s :poop:. It really comes down to can you afford to wait 13 - 30 seconds per edit to guarantee that your stuff is in order. Like I say, for me 100% easy win. And for files you really have to twiddle you can use mkOutOfStoreSymlink (blog link, not doc link).

EDIT: that bit with the file listing and wanting to “see his name back on the files he worked so hard to build”… wtaf.

In adittion to what @bme says, again, you can use xdg.configFile to just symlink your files into place, much like what stow does, the only difference is that the files don’t auto-update when you make changes. Don’t use mkOutOfStoreSymlink for this, you’ll thank me when come around to understanding nix properly.

I personally prefer this, partially for that reason. My dotfiles remain almost entirely OS-agnostic (with the exception of theming at the moment, though I have plans to fix that). They’re just usually deployed with home-manager, I could just drop the files into any host I need to by hand.

I like this especially for emacs, because it’s my #1 most essential tool (primarily because magit is much nicer than git or any other git frontend for that matter), though it’s been a very long time since I’ve even considered using it without home-manager, so buying fully into the ecosystem seems valid to me as well.

I think someone who rages against home-manager for those reasons has simply not understood how nix, home-manager, the module system, etc. work, and probably not yet maintained their dotfiles for a very long time. Most of my desktop use is in the terminal, home-manager makes plugins much easier to manage and maintain than any of the imperative BS you deal with on other distros.

You can probably somewhat safely discard their opinion as misinformed, or, dare I say it, sensationalized, because youtube channels don’t get popular unless they’re controversial…

There are however reasons to dislike the particular implementation of home-manager, especially if you’re competent enough to do it all yourself. Personally I’m looking forward to switching to hjem one day.

1 Like

I feel marginally guilty here, I don’t use it, because I don’t agree with the premise that editing files in place is that important (eval-last-sexp ftw), but a lot of people seem to get good usage out of it so :upside_down_face: ? I shall avoid leading people to attractive hazards in the future.

1 Like

That way of describing the relationship is just YT speak, personally it is a bad analogy, the important part would be the 13~30 seconds edit, I’m sure it hurts at the start when you’re setting up your system for the first time like I am at the moment, but it goes away once you have everything ready and committed to VC.

My advice would definitely be to go slowly, and absorb files as and when it there is a benefit. Then it doesn’t feel like such a chore. Having a properly setup nixd (or similar editor integration) is essential to getting as much right as possible before paying for an evaluation, and setting up a repl to inspect your options and configuration is also really helpful if you don’t want to be constantly waiting for eval to spit an error back out at you.

I’m not sure that xdg.configFile could be called a Symlink, the one I know creates a file in the expected location for that config that then links to the location you want, effectively avoiding configuring the path, but I get the idea, I would create a nix config that extends the base one I get when installing NixOS Gnome, and set it’s path with xdg.configFile, right?

Also you mention in your dotfiles README.md that for it to be OS agnostic you need flakes, maybe that used to be the case in the past?

I’m coming back to 2 points in this response, the VC part & how to extend the config in a different folder under my control instead of root, and Zsh.

Turns out Zsh is simpler to set up, the main change here is that instead of going for pkgs I use program, it will both load Zsh and OMZ and set the $ZSH env var that I would latter use to source the OMZ binary, and will look for the config in ~/.zshrc by default, only thing I’m missing is making my Ghostty load into Zsh by default, as well as root doing the same when I do sudo su:

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
    pkgs.fnm
    pkgs.ghostty
    pkgs.git
    pkgs.gitkraken
    pkgs.google-chrome
    pkgs.neofetch
    pkgs.nixfmt-rfc-style
    pkgs.vscode
    vim
    wget
  ];

  programs.zsh = {
    enable = true;
    ohMyZsh = {
      enable = true;
    };
  };

  programs.nix-ld.enable = true;
  programs.nix-ld.libraries = with pkgs; [
    # Add any missing dynamic libraries for unpackaged programs
    # here, NOT in environment.systemPackages
  ];
➜  ~ echo $ZSH
/nix/store/kk[...]cj-oh-my-zsh-2025-04-29/share/oh-my-zsh

Oh and I had to remove the part of my .zshrc that would set $ZSH:

# Path to your oh-my-zsh installation.
# case $(uname) in
# Darwin)
#   export ZSH="/Users/carlos/.oh-my-zsh"
#   ;;
# Linux)
#   export ZSH="$HOME/.oh-my-zsh"
#   ;;
# esac

fpath+=${ZSH_CUSTOM:-${ZSH:-~/.oh-my-zsh}/custom}/plugins/zsh-completions/src

source $ZSH/oh-my-zsh.sh

Note: I wanted to use NVM but couldn’t find a pkgs or program for it, had to use FNM, and then had to enable nix-ld for the linked node executable.

The part that keeps giving me trouble is that the /etc/nixos/configuration.nix is owned by root, I guess here’s where HomeManager would shine, I assume in a new install I would have to modify /etc/nixos/configuration.nix to import/link the HomeManager setup in my home folder ~/, which I would either just clone in there, or clone somewhere else & symlink in there, is that correct? do I need HomeManager if I do that or is mostly for the simpler syntax compared to the raw nix config schema?

You don’t need to use the file at /etc/nixos/configuration.nix. nixos-rebuild wants there to be a configuration available under <nixos-config>. So you can just set that yourself, either by shovelling it into the nix-path, or making a wrapper that does that for you.

nixos-rebuild switch -I nixos-config=/some/other/file

or

export NIX_PATH="nixos-config=/some/file:$NIX_PATH"
nixos-rebuild switch

etc etc

I’ve delved into videos, courses and NixOS & Flakes Book as well, ended up with a working dotfiles repo: GitHub - luchillo17/dotfiles: Nix ruled dotfiles

So far it’s working fine, it runs both in my laptop and in WSL, the only issue I have at the moment is that Cursor from Windows doesn’t work while VSCode does, I think the Microsoft WSL extension is just better than Cursor’s Anysphere WSL extension.

Well at the very least I got a good start at a Distro agnostic setup (have yet to test non NixOS distros).

Edit: The issue with cursor was about Anysphere expecting /bin/bash to exist, ended up symlinking the default one:

# dotfiles/hosts/modules/bash.nix
{ config, pkgs, ... }:
{
  system.activationScripts.binbash = {
    deps = [ "binsh" ];
    text = ''
      #!/bin/sh
      # This script creates a symlink to the bash binary in /bin
      # to ensure compatibility with scripts that expect bash to be in /bin/bash
      ln -sf /bin/sh /bin/bash
    '';
  };
}