Personal NixOS/MacOS declarative config.
  • Shell 38.1%
  • Nix 34.4%
  • Lua 27.5%
Find a file
2026-07-13 11:29:05 +02:00
.opencode chore: various updates 2026-04-29 21:57:03 +02:00
archive feat: remove nixpkgs-aot technique 2026-05-29 17:25:10 +02:00
aspects chore: system & vim update 2026-07-13 11:29:05 +02:00
hosts chore: various small adjustments 2026-07-13 11:03:13 +02:00
my feat: update & naming improvement 2026-06-08 21:13:22 +02:00
shared feat: add default avatar and use it in Noctalia 2026-06-08 12:36:37 +02:00
.gitignore zsh: add aider helper functions 2024-09-30 20:54:46 +02:00
.luarc.json feat(nvim): reorganize LSP keybindings under <leader>l 2025-06-21 21:01:29 +02:00
consts.nix feat: switch to per-host GPG keys 2026-05-04 17:16:30 +02:00
flake.lock chore: system & vim update 2026-07-13 11:29:05 +02:00
flake.nix feat: multiple changes 2026-06-15 13:34:47 +02:00
opencode.jsonc chore: minor adjustments 2026-04-19 01:28:04 +02:00
README.md feat: update & naming improvement 2026-06-08 21:13:22 +02:00
secrets.nix chore: set new key for working laptop 2026-06-09 11:22:10 +02:00
statix.toml chore: use inherit syntax for cleaner nix imports 2025-02-16 00:09:27 +01:00

Personal Nix-based Configuration for my Machines

Brainmade

Personal dotfiles and system configurations managed with Nix flakes.

This repo is brainmade. I use LLMs mostly to speedup mundane tasks, write boilerplates or to explore options. I never trust LLMs to decide what is good and what is bad.

Note: I switched to dendritic pattern recently. From implementation perspective it's functional and already convinient, but I still need to re-arrange some configurations and still experimenting with granularity.

What this repo is

This repository contains my personal configurations for NixOS, Ubuntu (via system-manager) and formerly macOS machines. It is done as a Nix flake which implements a top-level orchestrator for all my organized configurations and tools.

Path
flake.nix Entry point that wires everything together and defines flake outputs.
my/ All my custom Nix module options and helpers, all namespaced under my.*.
aspects/ Feature bundles: groups of NixOS, home-manager, and other modules. Can have associated folders with config files.
hosts/ Per-machine definitions that define system type and list of aspects to apply. Also contains host-specific modules.
secrets.nix Agenix recipients: declares which SSH keys can decrypt each secret. Encrypted secret files live in aspect companion folders.
shared/ files that shared between aspects.
archive/ Patterns documented for future reference. I add document here when remove some features from implementation. For example, when I remove support for Darwin when no longer have any macOS machine.

Standalone flake outputs

Some flake outputs are useful on their own — you don't need to install my system or home-manager configurations to use them.

Output Description Command
#overview Pretty-prints all hosts, aspects, and their features. nix run git+https://codeberg.org/ffloyd/nix#overview
#tidewave-app Tidewave desktop app packaged for x86_64-linux platform nix run git+https://codeberg.org/ffloyd/nix#tidewave-app
#tidewave-cli Tidewave CLI packaged for x86_64-linux platform nix run git+https://codeberg.org/ffloyd/nix#tidewave-cli

In a locally cloned repo, use . instead of the URL: nix run .#tidewave-app.

If you are new to Nix

If you are just curious about Nix, this repo might look a bit dense at first, and that is normal. As an entry point to the Nix ecosystem, I recommend to check out the following resources:

  • Official Nix(OS) website - for general information about Nix and NixOS.
  • Vimjoyer Youtube channel - this guy is amazing at explaining Nix and NixOS concepts in a very approachable and practical way. His channel was my entry point to Nix and I highly recommend it.
  • Understanding of the following tools is essential to understand this repo. Also check related Vimjoyer's videos.

Why Nix?

Now I can do "ASDF peasants" jokes.

On a more serious note, well... it's a toolkit that

  • can manage dotfiles
  • supports NixOS, other Linux system and even macOS
  • allows you to configure your system declaratively and reproducibly
  • makes reverting any changes as easy as reverting a commit
  • replaces ASDF, mise and similar "dev shell" tools. Provides reproducibility, not just replayability.
  • can be used to define and build Docker images (and they will be reproducible, not just replayable like with Dockerfiles)
  • and more

Just a single toolkit. While ASDF, homebrew, and other tools I mentioned much simpler and easier to use, they underperform in many aspects in comparison to Nix. One I mentioned here is the reproducibility vs replayability. Let me explain on example of a Dockerfile. When you have line like RUN apt-get update && apt-get install -y nodejs in your Dockerfile, it will install the latest version of nodejs at the moment of building the image. This is replayable, but not reproducible, because the same Dockerfile can produce different outputs at different times. It's only the tip of the iceberg, Dockerfiles are not designed for reproducibility in general and have non-deterministic nature. Like ASDF, like mise, and many other popular tools.

It's ok for many contexts to not have those strong guarantees and take some risks in exchange for simplicity. But personally, I prefer to master complex toolkit that is fun to learn, rather than waste time on simpler but boring tasks like fixing broken ASDF shims, dealing with Homebrew's quirks after system upgrade, and similar. With Nix such issues are less likely to happen, and when they do, they are usually easier and more comfortable and fun to fix. Moreover, Nix has a massive amount of interesting engineering decisions to learn from and become better engineer.

You cannot avoid having problems and errors, but you can choose which kind of errors you want to deal with and pick the ones that gives you more fun.

In the end, I may not save a lot of time with Nix - it takes time to learn and Nix is pretty complex. In exchange, I avoid a lot of frustration and have a lot of fun in the process. After all, let be honest - we do things not for efficiency, satisfaction is what we are really after, and Nix is a very satisfying tool to master. Like Vim, Emacs, Terraform - I have similar experience with them.

Setup on NixOS

The repository should be cloned into the nix folder in the user's home directory.

Installation command (in the root folder of the repository):

sudo nixos-rebuild switch --flake .

Setup on Ubuntu

The repository should be cloned into the nix folder in the user's home directory.

Install Nix with flakes enabled, then run:

# System-level configuration
nix run 'github:numtide/system-manager' -- switch --flake . --sudo

# User-level configuration
nix run home-manager/master -- switch --flake .

Configuration Philosophy

This configuration leans toward an objective-based organization approach instead of a more traditional category-based organization. Rather than grouping configurations by technical category (LSP, UI, Git tools), (when possible) modules are organized around specific objectives or goals.

When it makes sense, each feature module's naming and documentation focus on what it needs to achieve rather than what technology it uses. This approach is inspired by OKR (Objectives and Key Results) methodology, adapted for configuration management. The same philosophy drives both the Neovim configuration and the Nix module structure.

It helps me stay focused on the end goals and avoid concentrating too much on technical details.

Not every module here strictly follows this philosophy and it's by design. If you have done OKRs, you would understand that defining good objectives is hard and often takes a lot of time and practice. While it forces you to ask yourself "why" more often, sometimes it's just better to put something in a separate file without overthinking.

Architecture

Over time I designed or adopted various concepts to organize configurations.

flake.parts and dendritic pattern

I was truly impressed by the idea of a dendritic pattern. TL;DR: it is about shifting from:

Each Nix file structure is defined by its caller. You need to understand where a file is used to understand how to read it. Shared values are passed via specialArgs and imports.

to something less cognitively demanding:

Each Nix file except for flake.nix is a flake.parts module. Knowing what flake.parts is and how it works is enough to understand the structure of any file in the repository. You can directly access any value from any other module.

It also allows all Nix files to be merged together automatically using import-tree instead of manually importing each file.

Oh my

I had to go beyond standard flake.parts capabilities. All Nix files that implement custom options and their behavior are located in the my/ folder. All custom options are prefixed with my. to avoid potential conflicts with options from other flake.parts modules.

Aspects and hosts

Simply speaking, main building blocks of the configuration are:

  • NixOS modules - for NixOS system-level configuration.
  • home-manager modules - for user-level configurations and for shared modules that work both for NixOS and macOS.
  • (in the past) nix-darwin modules - for macOS system-level configurations.
  • system-manager modules - for system-level configs on Ubuntu.

While for one host it would be ok to define those modules and import them into one machine configuration, I have multiple machines and their module sets are not identical. So I introduced my.hosts and my.aspects options.

Aspect is a collection of modules that together implement a specific feature set.

Host is a concrete machine definition that I use, with its hostname, username, system type, and list of aspects that should be enabled on it.

Actual machine configurations are generated from host definitions and aspects. See my/hosts.nix and my/aspects.nix for options documentation and implementation details.

The aspects/ folder contains aspect definitions. Each aspect is either a single file or a folder with multiple files.

The hosts/ folder contains host definitions (hosts/host-alias.nix) and host-specific modules (hosts/host-alias/*.nix).

Shared constants

my.consts is a loosely typed attrset that serves as a container for global constants. flake.parts makes it available for any Nix file. Shared values like email addresses and locale settings are set in consts.nix.

Secret management

Secrets are managed with agenix. Decription happens at activation time into an in-memory file system (or ~/.secrets/ for home-manager). Encrypted .age files live in companion directories next to their aspect definitions. The secrets.nix file declares which SSH keys can decrypt each secret.

Helpers

Nix has an impressive standard library, but sometimes I need specific functions that are not available there. When possible, I keep them module-local. For the rest, I have my.helpers - a collection of helper functions that are used across different modules and aspects. Check my/helpers.nix for details.

Directly symlinked dotfiles

Configuration files generated by Nix are immutable. Updating them requires re-evaluating the configuration, which takes 20-50 seconds on my machines. While it is ok or even beneficial for most of my cases, it is not ideal for dotfiles that I edit often or need a faster feedback loop for.

Good examples are AI-related files, Neovim configuration, and terminal configuration.

Dotfiles live in companion directories next to their aspect definitions (e.g. aspects/desktop/niri-noctalia/config.kdl next to aspects/desktop/niri-noctalia.nix). Not every file in those folders is symlinked — only the ones I edit often. my.helpers.mkDirectSymlink creates symlinks from the repository directly to the home directory, bypassing the Nix store. It takes a path relative to the calling file and resolves it to a repo-relative path under ~/nix/.

# In aspects/terminal.nix:
home.file.".config/kitty/kitty.conf".source = mkDirectSymlink config ./terminal/kitty.conf;

This allows editing dotfiles in place without waiting for Nix to rebuild.

Common tasks

Inspection

  • Overview of hosts, aspects, and adjustments: nix run .#overview

Maintenance

  • Format code: nix fmt .
  • Check flake validity: nix flake check --all-systems
    • for the deeper check that includes buiding everything that can be built locally: nix flake check --build-all (not only the current host configuration)
  • Upgrade dependencies: nix flake update
  • Upgrade specific input: nix flake update agenix

Secrets

  • Edit a secret: agenix -e aspects/desktop/niri-noctalia/foo.age
  • Rekey all secrets after changing recipients: agenix --rekey
  • The secrets.nix file declares which SSH public keys can decrypt each secret.

Validation & Testing (NixOS)

  • Test if configuration builds: nixos-rebuild dry-build --flake .
  • Preview activation changes: nixos-rebuild dry-activate --flake .
  • Lint: statix check

Applying Changes

See aliases in ./aspects/base/nix.nix.