Dotfiles, the nix way

Matteo - Oct 30 - - Dev Community

I've recently started exploring the Nix ecosystem (I got hooked by Vimjoyer's videos) and decided to give it a try (I even installed NixOS on my laptop, but that will come in a later article).

gif

Dotfiles

First things first, what are dotfiles ? They are basically configuration files that are usually preceded by a .. or that are usually kept inside a folder called .config in your home directory. They are used to configure per user settings for apps that support it. For example, you could use it to change the keybindings in your i3 configuration, or pimp your neovim config or just change the color scheme for your favorite terminal. Why would you want to do that, I hear you ask? Why not, I would answer...
gif

Ok, let's be serious for a second; if you went through the (kind of) trouble that installing Linux is (though that obviously depends on the distro you choose), then you'd probably want to customize to be more "you" or to better fit your needs. Here's an example: I have pretty bad eyesight and tend to prefer some specific colors for windows, text and stuff; after a while, I found out that the Catppuccin Mocha theme is not as hard on my eyes than some other (or even the Gnome or KDE default dark mode, for that matter), so I like for as many of the applciations as I daily drive to use that same theme (plus, I think it looks pretty).

Here's how you would configure rofi to use Catppuccin Mocha:

  • create a mocha.rasi file that will contain all the theme customization rule
  • create a config.rasi file in .config/rofi/ that will contain the import rule for the theme you want: @theme "catppuccin-mocha" You might have noticed what my problem is with these kind of configuration files; most of them are custom file formats! This might be fine if you have one or two configuration files, but it gets old really quick if you have more than that.

My Journey with Dotfiles

Step 1: files ending in dots

I started playing with dotfiles when I first installed Arch (btw), and the first approach I had was to just write my files directly in the .config directory. That phase ended quickly once I mistakenly wiped my OS and lost all said files...

Happens to the best of us, right?
gif

Step 2: version control for the win

Once I got my system up and running again, I decided to version control those files (you live you learn, right?)
Here's the problem with that approach: you either version control the whole .config directory, or you can create many small repos for each program because, spoiler alert, anything can write stuff in that directory.

Step 3: Stowing

So now you have a git repo containing all your dotfiles but cannot directly clone into to replace your .config folder, what do you do? You find out there's a tool that could fix all these problems: GNU Stow!

This tool allows you to create symlinks to anywhere and keep all your original files in a single folder (sound familiar?)

I'd been on this step for a long time and kind of liked it, to be honest. But then I started hearing about Nix more and more often and so decided to take a look at it.

Step 4: Manage your home

I'd read about Nix a while ago but had never explored it correctly. That changed when I got some time off work to tinker with some stuff. That's when I heard about home-manager and decided to give it a try.

Home Manager

Home-manager is a tool that allows you to declare your config files and put them wherever they need to be. You can declare almost anything directly using the Nix DSL, but you can also simply copy files into their correct directory and even clone directly a repo from Github (this is the method I use to get my neovim configuration instead of rewriting it to use Nix).

Let's take a look at a simple home-manager home.nix file:

{config, pkgs, ...}
{
    home.stateVersion = "24.05";
    home.packages = [
        pkgs.fzf
        pkgs.tmuxifier
    ];
    home.file = [
        ".config/waybar/mocha.css".source= ./styles/mocha.css;
    ];
}
Enter fullscreen mode Exit fullscreen mode

This little configuration will ensure that both fzf and tmuxifier are on your user's $PATH and copies over a css file from your directory into the appropriate .config directory.

But this is not all we can do with home-manager (again, I highly encourage you to read the documentation); let's see how we can configure some more stuff.
This is how you can configure kitty, a tty:

  programs.kitty = {
    enable = true;
    settings = {
      "tab_bar_min_tabs" = 1;
      "tab_bar_edge" = "bottom";
      "tab_bar_style" = "powerline";
      "tab_powerline_style" = "slanted";
      "tab_title_template"= "{title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}";
    };
    font = {
    name = "JetBrainsMono Nerd Font";
    };
    themeFile = "Catppuccin-Mocha";
  };
Enter fullscreen mode Exit fullscreen mode

This enables kitty, sets some configuration stuff, like the topbar style title template string, but ti also sets up the font to use in the terminal and the theme the terminal will use.

How about writing configs when home-manager does not have a module for it (or you can't find it)? Well, here is how I configure tmux using home-manager:

programs.tmux = {
    enable = true;
    plugins = with pkgs.tmuxPlugins; [
      catppuccin
    ];
    extraConfig = ''
      set -g prefix C-s
      set -g mouse on
      set-option -g status-position top

      set -g @plugin 'tmux-plugins/tpm'
      set -g @catppuccin_flavour 'macchiato'
      set -g @plugin 'kenos1/tmux-cht-sh'
      set -g @plugin 'jimeh/tmuxifier'
      set -g @catppuccin_window_left_separator ""
      set -g @catppuccin_window_right_separator " "
      set -g @catppuccin_window_middle_separator " █"
      set -g @catppuccin_window_number_position "right"

      set -g @catppuccin_window_default_fill "number"
      set -g @catppuccin_window_default_text "#W"

      set -g @catppuccin_window_current_fill "number"
      set -g @catppuccin_window_current_text "#W"

      set -g @catppuccin_status_modules_right "directory user host session"
      set -g @catppuccin_status_left_separator  " "
      set -g @catppuccin_status_right_separator ""
      set -g @catppuccin_status_right_separator_inverse "no"
      set -g @catppuccin_status_fill "icon"
      set -g @catppuccin_status_connect_separator "no"

      set -g @catppuccin_directory_text "#{pane_current_path}"
      run '~/.tmux/plugins/tpm/tpm'
    '';
  };
Enter fullscreen mode Exit fullscreen mode

This configuration here allows you to see how you can pass "raw" configuration values via extraConfig and how to setup plugins taken directly from nixpackages.

The last example I want to show you, is how to retrieve configuration files from a github repository and copy it over to the correct .config folder. This goes in a bit deeper in the Nix DSL, but I think it still keeps pretty readable:

let
  neovim_config = pkgs.fetchFromGitHub {
    owner = "matfire";
    repo = "config.nvim";
    rev = "044cb8670307bb3195607b90229f26ffcb61d46d";
    sha256 = "sha256-tzWcYrRGX82kBmPc50LjQmXfNjBoT/sLNanH470NuAE=";
  };
in
{
    home.file = {
        ".config/nvim".source = neovim_config;
    };
}
Enter fullscreen mode Exit fullscreen mode

This will automatically clone and verify (with the sha256 you provide) the right repository and commit, and then clone (or copy) it over to the right place.

gif

The best of both worlds ?

Personally, I really like doing configs like this; it lets me keep it all in one format (with the occasional exception, obviously) and be sure that whatever OS I use this on, I'll get the same programs & configs each time.

But it still requires you to learn a new language (or the basics of it, at least) and it's a functional language, which may be a blocker for some. The first experience I ever had with a functional programming language was Haskell back in university, so I can understand the animosity with this principle.

Still, I believe it worthwhile if you daily drive Linux and have a lot of config files to learn and use this kind of system.

. . . . . . . . . . . . .