My Personal Nix/NixOS Configurations
  • Nix 84.8%
  • Lua 15.1%
  • Nu 0.1%
Find a file
2026-06-04 22:39:07 -06:00
hosts docs: update readme 2026-06-03 23:00:12 -06:00
keys feat(modules.home): declarative ssh keys. This is probably a terrible idea 2026-05-25 23:15:42 -06:00
modules refactor(modules): change importing standard so modules still look normal 2026-06-03 20:06:03 -06:00
packages feat(packages.git): add some new aliases 2026-06-01 21:16:13 -06:00
sources chore: update noctalia 2026-06-04 20:02:01 -06:00
.editorconfig feat(packages.neovim): import existing config 2026-04-23 11:56:06 -06:00
.envrc feat: devshell for formatting and secrets cli 2026-05-19 19:56:24 -06:00
.gitignore fix: ignore shell 2026-05-20 11:24:51 -06:00
flake.nix refactor: use self in flake 2026-06-03 18:02:40 -06:00
LICENSE.md chore: switch from mit to gpl 2026-05-08 21:38:13 -06:00
README.md docs: update readme 2026-06-04 22:39:07 -06:00
secrets.nix feat(modules.home): declarative ssh keys. This is probably a terrible idea 2026-05-25 23:15:42 -06:00
shell.nix feat: switch to npins and update everything 2026-05-22 14:30:58 -06:00

My Personal Nix/NixOS Configurations ❄️

If you're looking at this config, you're probably interested in ways to organize and setup your own config.

Before you read the reasons I structure this config in the way I do, here's some other walls of text you might find interesting with my opinion attached:

  • Synaptic Standard

    The synaptic standard is a really good (albeit incomplete) set of standards going over how to structure your config. It's a super good structure for beginners and will stay useful even when you are more experienced with the nix language. It doesn't focus on portability at all, which causes a few personal distastes for me, but that isn't important for most users and I'd still say it's the best framework out there; I take a lot of inspiration from it in my config.

  • Dendritic Pattern

    The dendritic pattern is the most widely used config format. It undeniably has some advantages, such as making your code more flexible and portable, but it totally obfuscates everything. It also uses a module system to evaluate the module system, which is far too redundant for my taste. I already don't really love the module system's use of global scope, and all that coupled with the fact that dendritic makes the config really slow to eval makes me wary of using it at best. Also see Neurotic, an implementation of dendritic without flake-parts. It's an improvement but I still think it over-abstracts everything.

My Config's Design Philosophies

How I Configure Programs

I use wrappers to configure (the vast majority of) my packages.

For most people, I would say just placing your config files in ~/.config like on any other distro can totally work. If you're looking for something more nix-y, home-manager is probably the first thing you'll find, especially because it's an official project. Which is unfortunate, because home-manager SUCKS. It's extremenely slow, adding a ton of time to your config evaluation and to your boot steps, very complicated under the hood, and simply has a better alternative: hjem. Hjem simply links files and does it quickly and efficiently. I use this to confgiure some things that can't be wrapped. If you want to use it to configure things in nix, hjem-rum is a good library to help do that.

However, while dotfile managers are useful, I prefer to, when possible, configure programs by wrapping them. Wrappers let you bundle the configuration with the program itself, meaning, instead of running neovim, you run neovim with an environment variable set that moves its configuration directory to the nix store, and also get to declare the contents of that directory. Also, compared to hjem/home-manager, iterating on them is much faster 'cause you can use nix run or a development shell instead of having to rebuild your whole config.

There are two ways I would recommend doing this: hand-rolling wrappers, or using adios-wrappers. The reason I like adios-wrappers over other libraries is that it's very simple and adds almost no overhead when compared to manual wrapping, while things like nix-wrapper-modules add a lot of time to eval and don't really use the module system properly, even though they totally work for configuring stuff.

To wrap something manually, best practice is to create a custom package file for it and then use pkgs.callPackage on it. Here's a simple example wrapping bash:

{
  bash,
  buildEnv,
  git,
  makeWrapper,
  ripgrep,
  symlinkJoin,
  writeText,
  ...
}: let
  initFile = writeText "init.sh" ''
    export PS1="\u on \h in \w \n$ "
  '';

  env = buildEnv {
    name = "fish-runtime-env";
    pathsToLink = ["/bin"];
    paths = [
      git
      ripgrep
    ];
  };
  # When you use `makeWrapper`'s wrap program, the result is just a single executable.
  # `symlinkJoin` makes sure other important things such as .desktop files are also symlinked into the resulting derivation.
in
  symlinkJoin {
    name = "bash";
    paths = [
      bash
    ];
    nativeBuildInputs = [
      makeWrapper
    ];

    # `wrapProgram` is a helper function from nixpkgs for wrapping things.
    # Here's its reference: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/setup-hooks/make-wrapper.sh
    postBuild = ''
      wrapProgram $out/bin/bash \
        --add-flags "--init-file ${initFile}" \
        --prefix PATH : ${env}/bin
    '';

    # Some Attributes of the original package need to be also applied to the wrapped version.
    # This usually only applies for more complicated things like shells/window managers.
    passthru.shellPath = bash.passthru.shellPath;
  }

Hand-rolling wrappers like this would be perfect for me, however, I like to configure my programs in nix, meaning I would need to make and maintain a generator for each program. Instead, I use adios-wrappers so I can profit off all the hard work of its contributors and have a bit less maintainance on my part.

How I Structue Modules

My modules are strucuted in a way that keeps them portable. When I say portable, I mean that you could import my ./modules directory and be able to add those modules to your own config. This is important to me because I have some shared devices that import this config and because it allows me to use the same framework when editing my personal config as I do when editing any external libraries.

I import all of my modules using a recursive imports function, similar to this one from synaptic standard.