Personal dotfiles and system configurations managed with Nix flakes.
This repository contains my personal configurations for NixOS and macOS machines. It uses Nix flakes to provide reproducible system configurations for:
- Framework laptop running NixOS with Hyprland
- Desktop machine running NixOS
- Work MacBook with nix-darwin
The repository includes an extensive Neovim configuration (~30% of all config lines) with AI integrations.
Standard NixOS modules are used for NixOS configuration. home-manager is used for user-level configurations and for sharing modules between NixOS and macOS. nix-darwin is used for macOS system-level configurations.
Home Manager's mkOutOfStoreSymlink is used to directly link dotfiles from the repository to the home directory bypassing the Nix store.
This allows editing dotfiles in place without creating a new generation for each change.
Sensitive data is stored in private.nix and hardware-configuration.nix files, which are encrypted using git-crypt.
As an output, the flake provides configurations for several machines. Each machine has a different set of modules enabled.
NixOS hosts use merged configuration that includes both NixOS and Home Manager modules at the same time. MacOS hosts use separate configurations for nix-darwin and Home Manager: I change nix-darwin configuration much less frequently, so there is no point to wait for additional 10+ seconds when updating Home Manager part.
This repository uses a three-tier system for AI coding assistant configuration (Claude Code and OpenCode):
-
Shared "as is" (
dotfiles/ai-shared/): Content identical for both tools (e.g., coding rules). These files are symlinked directly and are editable without rebuild. -
Shared snippets (
hm/development-environment/ai-tooling/): Content that needs tool-specific frontmatter (e.g., commit/review instructions). These files are used to generate tool-specific commands/agents and require rebuild to see changes. -
Experimental (tool-specific directories like
dotfiles/claude/commands/,dotfiles/opencode/agent/): Single-tool experimental files. These are symlinked directly and editable, but require rebuild to discover new files.
Home directories like ~/.claude/commands/ and ~/.config/opencode/agent/ contain both generated files (immutable, from Nix store) and experimental files (editable, from dotfiles). This allows stable shared commands to coexist with experimental work-in-progress commands.
- Experiment: Create file in tool-specific directory (e.g.,
dotfiles/claude/commands/experimental.md), rebuild once to create symlink, then edit freely. - Share: When stable and needed for both tools:
- If content is identical → move to
dotfiles/ai-shared/ - If needs tool-specific adjustments → extract to
hm/development-environment/ai-tooling/, add generation inai-tooling.nix
- If content is identical → move to
- No rebuild needed: Editing existing files in
dotfiles/ai-shared/or tool-specific experimental files - Rebuild required: New experimental files, changes to shared snippets in
hm/development-environment/ai-tooling/
| Files | Purpose |
|---|---|
flake.nix |
The main entry point, defines configuration for all hosts. |
globals.nix |
An attrs set with some global values. Propogated to all modules. |
private.nix |
An attrs set with some global values that are personal data. Encrypted. Propogated to all modules. |
hosts/* |
Each host has a folder with machine-specific configuration. hardware-configuration.nix is a hardware configuration and also encrypted. |
hm/*.nix |
Configuration modules that shared between NixOS and MacOS. |
hm/development-environment/ai-tooling/ |
AI shared snippets for generation (immutable, rebuild required for changes). |
nixos/*.nix |
NixOS configuration modules. Can include home-manager configurations that are NixOS-specific. |
darwin/*.nix |
nix-darwin configuration modules. |
dotfiles/ai-shared/ |
AI content shared identically between tools (editable, no rebuild needed). |
dotfiles/claude/, dotfiles/opencode/ |
Tool configuration and experimental AI files (editable, rebuild to discover new files). |
dotfiles/* (other) |
These files are linked directly, not through Nix store. It allows to edit them in place without creating a new generation for each change. |
The reposiory should be cloned into nix folder in the user's home folder.
Installation command (in the root folder of the repository):
sudo nixos-rebuild switch --flake .The reposiory should be cloned into nix folder in the user's home folder.
Install Nix and homebrew. Homebrew will be used only for installing casks. Then run the following command in the root folder of the repository:
nix --extra-experimental-features nix-command --extra-experimental-features flakes run nix-darwin -- switch --flake .
nix --extra-experimental-features nix-command --extra-experimental-features flakes run home-manager/master -- switch --flake .This configuration uses an objective-based organization approach instead of more traditional category-based organization. Rather than grouping configurations by technical category (LSP, UI, Git tools), modules are organized around specific objectives or goals.
Each module focuses on what you want 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.
On practice, nix module files are named after the objective/goal they designed to achieve and start with a comment explaining the objective.
Each nix module may contain many sub-modules that implement technical features needed to achieve the objective.
So, it's ok to comment a sub-module with "setup hyprland", but the file with it should be about "desktop environment setup"/desktop.nix (which explains reason we need hyprland) and not "hyprland configuration"/hyprland.nix.
It helps me to stay focused on the end goals and avoid concentrating too much on technical details.
Not an every module here strictly follows this philosophy and it's by design. If you did OKR, you would understand that defining good objectives is hard and often takes a lot of time and practice. While it forces to ask yourself "why" more often, sometimes it's just easier to put something in a separate file without overthinking.
Define module as a set of small sub-modules
Following defined configuration philosophy, I want each module to be a set of small sub-modules, each responsible for a specific feature. While the top-level module in a file is responsible for a specific objective, it may include multiple sub-modules that implement the technical details needed to achieve that objective. The benefit of this approach is that for every option, I can easily say which technical feature it belongs to and which higher-level objective it helps to achieve.
Every Nix module (NixOS, home-manager, nix-darwin) has imports attribute that can be used to include other modules.
But also it can consume "inline" sub-modules:
{...}:
{
imports = [
# include other file
./submodule1.nix
# inline sub-module
{
options.bla-bla.enable = true;
}
};
}I cannot just split configuration using comments because Nix by default forbids duplicate keys in an attrs set:
{
#
# Feature A
#
# utils for feature A
home.pkgs = with pkgs; [ pkgA pkgB ];
# ...
#
# Feature B
#
# utils for feature B
home.pkgs = with pkgs; [ pkgC pkgD ]; # <-- error: duplicate key
}Such behavior is inconvenient when you want to split a module into smaller isolated parts each responsible for a specific feature.
mkMerge function from nixpkgs.lib can also be used to merge multiple attrs sets into one, but it has some critical limitations (cannot merge modules that define options or imports).
- Check flake validity:
nix flake check - Test if configuration builds:
nixos-rebuild dry-build --flake . - Preview activation changes:
nixos-rebuild dry-activate --flake . - Format code:
nix fmt . - Lint:
statix check
- Apply configuration:
nixos apply(or use aliasos-rebuild) - Apply to boot only:
nixos apply --no-activate --install-bootloader(or aliasos-rebuild-boot)
- Upgrade dependencies:
nix flake update - Upgrade specific input:
nix flake update nixpkgs-aot - Clean old generations:
nixos generation delete --min 5 --all(or aliasos-gc)
When nixpkgs-unstable has build failures blocking full system upgrade, use pkgs-aot to access newer package versions selectively.
{ pkgs, pkgs-aot, ... }:
{
environment.systemPackages = [
pkgs.firefox # from main nixpkgs
pkgs-aot.neovim # newer version with isolated dependencies (from nixpkgs-aot)
];
}Note: pkgs-aot.neovim uses its complete dependency tree from nixpkgs-aot (separate from pkgs). Use for self-contained applications, be careful with libraries and global services.