Personal NixOS & nix-darwin configuration with a modular, hierarchical architecture.
"Works on my machine" β "Works on every machine"
Nix is a purely functional package manager that treats system configuration as code. The same configuration always produces the same system β whether you're setting up a fresh laptop or rebuilding after a disaster. Made a mistake? Just boot into a previous generation and you're back to a working state in seconds.
Everything is declarative: packages, services, dotfiles, even your desktop environment. No more scattered configs or forgotten setup steps. Your entire system lives in version-controlled .nix files that work across all your machines β Linux desktops, macOS laptops, headless servers.
Updates are atomic (they fully apply or don't touch anything), and different package versions coexist peacefully. No dependency hell, no "I updated X and now Y is broken". Just reproducible, reliable systems.
- π₯οΈ Multi-platform β Same structure for NixOS and macOS (nix-darwin)
- π₯ Multi-user ready β Each user can have different modules enabled
- π― Hierarchical enables β Enable at any path level:
home.browsers.enableorhome.browsers.vivaldi.enable - βοΈ Live-editable dotfiles β Config files are symlinked to this repo, edit in place without rebuild
- π Auto-discovery β Drop a
.nixfile in any module directory, it's automatically imported
- Quick Start
- Architecture
- Guides
- Secrets Management (SOPS)
- Vantage Infrastructure Stub
- Host Configuration
- License
# Clone the repository
git clone https://github.com/gdr/dot.git ~/Workspaces/gdr/dot
cd ~/Workspaces/gdr/dot
# Apply configuration
# NixOS:
sudo nixos-rebuild switch --flake .#<hostname>
# macOS:
darwin-rebuild switch --flake .#<hostname>graph TD
subgraph Inputs["Flake Inputs & Outputs"]
FI["flake.nix Inputs<br/>(nixpkgs, nix-darwin, home-manager, sops-nix, vantage)"]
FO["flakeOutputs<br/>(nixosConfigurations, darwinConfigurations, packages, devShells, checks, formatter)"]
FI --> FO
end
subgraph Hosts["Host Configurations"]
NG["nix-goldstar<br/>(NixOS Desktop)"]
NO["nix-oldstar<br/>(NixOS Server)"]
MB["mac-brightstar<br/>(Darwin Laptop)"]
FO --> NG
FO --> NO
FO --> MB
end
subgraph Profiles["Profiles & Users"]
U["User Defaults<br/>(hosts/users/dgarifullin.nix)"]
P["Profiles & Enables<br/>(developer, desktop, server, gaming)"]
NG --> U
NO --> U
MB --> U
U --> P
end
subgraph Modules["Module System"]
HM["User Modules (mkModuleV2)<br/>modules/home/*"]
SM["System Modules (mkSystemModuleV2)<br/>modules/systems/{all,linux,darwin}/*"]
P --> HM
P --> SM
end
subgraph Targets["System State & Config"]
DOT["Live-Editable Dotfiles<br/>(mkDotfilesSymlink β ~/.config/*)"]
SECR["SOPS Secrets<br/>(sops-nix / age / Bitwarden integration)"]
HM --> DOT
SM --> SECR
end
.
βββ flake.nix # Entry point - defines hosts and imports
βββ hosts/
β βββ users/ # User defaults (imported by machines)
β β βββ dgarifullin.nix
β βββ machines/ # Machine configurations
β βββ nix-goldstar/ # NixOS host
β β βββ default.nix
β β βββ hardware-configuration.nix
β βββ mac-brightstar/ # Darwin host
β βββ default.nix
βββ lib/
β βββ default.nix # Helper functions (mkModule, mkDotfilesSymlink)
βββ modules/
β βββ _core/ # Core module infrastructure
β β βββ registry.nix # Module registry builder
β β βββ user.nix # hostUsers options & home-manager setup
β βββ systems/
β β βββ all/ # Cross-platform system modules
β β β βββ fonts.nix
β β β βββ nix/
β β β β βββ gc.nix
β β β β βββ settings.nix
β β β βββ shell/
β β β βββ git.nix
β β β βββ ssh.nix
β β βββ linux/ # Linux-only system modules
β β β βββ desktop/
β β β β βββ awesomewm/
β β β β βββ hyprland/
β β β βββ graphics/
β β β βββ keyboards/
β β β βββ networking/
β β β βββ sound.nix
β β βββ darwin/ # macOS-only system modules
β βββ home/ # User-level modules (enabled hierarchically)
β βββ browsers/
β βββ cli/
β βββ desktop/
β β βββ appearance/
β β βββ services/
β β βββ utils/
β β βββ widgets/
β βββ editors/
β βββ games/
β βββ media/
β βββ messengers/
β βββ security/
β βββ shell/
β βββ terminal/
βββ pkgs/ # Custom packages
| Type | Location | Enabled via | Scope |
|---|---|---|---|
| System (All) | systems/all/ |
modules.system.all.<name>.enable |
System-wide, cross-platform |
| System (Linux) | systems/linux/ |
modules.system.linux.<name>.enable |
System-wide, Linux only |
| System (Darwin) | systems/darwin/ |
modules.system.darwin.<name>.enable |
System-wide, macOS only |
| User | home/ |
hostUsers.<user>.modules.<path>.enable |
Per-user, hierarchical enables |
- Create host directory:
mkdir -p hosts/machines/my-new-host- Create
default.nix:
# hosts/machines/my-new-host/default.nix
{ config, lib, pkgs, ... }:
let
importUser = name: import ../../users/${name}.nix { inherit lib; };
in
{
imports = [ ./hardware-configuration.nix ];
# User configuration
hostUsers.dgarifullin = importUser "dgarifullin" // {
enable = true;
keys = [{
name = "my-new-host";
type = "rsa";
purpose = [ "git" "ssh" ];
isDefault = true;
}];
# Hierarchical module enables
modules = {
home.cli.enable = true;
home.shell.enable = true;
home.editors.enable = true;
# Or enable specific modules:
# home.browsers.vivaldi.enable = true;
};
};
networking.hostName = "my-new-host";
# System modules
modules.system.all = {
fonts.enable = true;
nix.settings.enable = true;
nix.gc.enable = true;
shell.ssh.enable = true;
shell.git.enable = true;
};
# Linux-specific (remove for Darwin)
modules.system.linux = {
desktop.hyprland.enable = true;
networking.networkmanager.enable = true;
sound.enable = true;
};
time.timeZone = "Europe/Moscow";
}- Generate hardware config (NixOS):
nixos-generate-config --show-hardware-config > hosts/machines/my-new-host/hardware-configuration.nix- Add to
flake.nix:
nixosConfigurations.my-new-host = mkNixosConfiguration ./hosts/machines/my-new-host;
# or for Darwin:
darwinConfigurations.my-new-host = mkDarwinConfiguration ./hosts/machines/my-new-host;- Create user defaults:
# hosts/users/newuser.nix
{ lib, ... }:
{
enable = lib.mkDefault false;
fullName = lib.mkDefault "New User";
email = lib.mkDefault "newuser@example.com";
github = lib.mkDefault "newuser";
extraGroups = lib.mkDefault [ "wheel" "audio" "video" ];
}- Enable in host config:
# hosts/machines/my-host/default.nix
hostUsers.newuser = importUser "newuser" // {
enable = true;
keys = [{
name = "my-host";
type = "rsa";
purpose = [ "git" "ssh" ];
isDefault = true;
}];
# Hierarchical module enables
modules = {
home.core.enable = true;
home.shell.enable = true;
home.media.vlc.enable = true; # specific module
};
};Configure modules per-user using hierarchical enables:
hostUsers.dgarifullin = importUser "dgarifullin" // {
enable = true;
modules = {
# Enable entire categories
home.browsers.enable = true; # enables vivaldi, chromium, etc.
home.editors.enable = true; # enables neovim, cursor, etc.
# Or enable specific modules
home.media.vlc.enable = true; # just vlc
home.media.spotify.enable = true;
};
};Hierarchical enables:
home.enable = trueβ enables ALL home moduleshome.browsers.enable = trueβ enables all browsershome.browsers.vivaldi.enable = trueβ enables just vivaldi
Module paths follow the directory structure: home.<category>.<module-name>
# modules/home/tools/my-tool.nix
{ lib, pkgs, ... }@args:
lib.my.mkModuleV2 args {
description = "My awesome tool";
platforms = [ "linux" "darwin" ]; # optional, defaults to both
module = {
# Cross-platform config (goes to home-manager.users.*)
allSystems.home.packages = [ pkgs.my-tool ];
# Or platform-specific
nixosSystems.programs.my-tool.enable = true;
darwinSystems.homebrew.casks = [ "my-tool" ];
};
}# modules/systems/linux/services/my-service.nix
{ lib, pkgs, config, ... }@args:
let
enabledUsers = lib.filterAttrs (_: u: u.enable) config.hostUsers;
in
lib.my.mkSystemModuleV2 args {
namespace = "linux";
description = "My service";
module = _: {
# System-level NixOS options
services.my-service.enable = true;
# User packages via home-manager
home-manager.users = lib.mapAttrs (name: _: {
home.packages = [ pkgs.my-tool-client ];
}) enabledUsers;
};
}Modules are auto-discovered recursively! Just create your .nix file in the right directory and it's automatically imported. No manual import lists needed.
Dotfiles support two modes on a per-module basis:
- π¦ Settled / Store Mode (
live = false, default): Copies dotfiles into/nix/store. Store paths are captured in NixOS / Home Manager generations, allowing NixOS & GRUB generation rollbacks (nixos-rebuild switch --rollbackor GRUB boot menu) to restore historical dotfile revisions cleanly. - π οΈ Live Mode (
live = true): Symlinks point out-of-store directly to$DOTFILES_DIR. Edit files directly during active development and changes apply immediately withoutnixos-rebuild switch.
- In Module Definition:
dotfiles = {
path = "ghostty/config";
source = "modules/home/terminal/ghostty/dotfiles/config";
live = true; # Set to true during active development
};- Per-Module Override in Host Config:
# hosts/machines/nix-goldstar/default.nix
modules.home.terminal.ghostty.dotfilesLive = true; # Enable live edit for active dev
modules.home.editors.neovim.dotfilesLive = false; # Store mode for settled neovim configSecrets across the repository are managed using sops-nix with age encryption.
Host age keys are derived directly from SSH host keys (/etc/ssh/ssh_host_ed25519_key.pub), eliminating the need to store secondary key files on machine hosts:
# Derive host age key from SSH host key
nix-shell -p ssh-to-age --run 'ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub'- Linux / NixOS:
~/.config/sops/age/keys.txt(XDG standard compliant). - macOS / Darwin:
~/Library/Application Support/sops/age/keys.txt(Environment variableSOPS_AGE_KEY_FILEis automatically set bymodules.system.all.sops.enable).
A shell wrapper (sops) is provided in Zsh (common.zsh). If SOPS_AGE_KEY is not present, it dynamically fetches the per-machine SOPS key from Bitwarden CLI (bw get notes "SOPS Age Key <hostname>") and caches it in RAM (/tmp/.sops-age-key-<hostname>-<uid>) for 15 minutes.
Secrets are specified in .sops.yaml with creation rules targeting host secrets paths (e.g., hosts/machines/<host>/secrets/*). Edit or create secrets using:
sops hosts/machines/<host>/secrets/<secret-name>vantage is an optional infrastructure flake input containing private service modules (Consul, Nomad, Vault, Vault Agent sidecar, remote builder configuration, Consul DNS).
To ensure the repository remains fully open-source, evaluation-ready, and buildable on public CI without needing private SSH keys:
- By default,
flake.nixpointsvantageto a public stub repository:github:GDR/dot-stubs?dir=vantage - The stub exposes no-op modules and default options, allowing all host configurations (
nix-goldstar,nix-oldstar,mac-brightstar) to evaluate cleanly without private repo access.
When deploying to active infrastructure nodes (nix-oldstar or mac-brightstar), override the vantage input to point to the private repository:
# Manual override during rebuild:
nixos-rebuild switch --flake .#nix-oldstar --override-input vantage git+ssh://git@github.com/GDR/vantage
# Or using Makefile helper targets:
make nix-oldstar
make mac-brightstar| Host | Platform | Description |
|---|---|---|
nix-goldstar |
NixOS (x86_64-linux) | Desktop workstation with Hyprland & NVIDIA |
nix-oldstar |
NixOS (x86_64-linux) | Server & remote builder (ThinkPad T480 homelab) |
mac-brightstar |
Darwin (aarch64-darwin) | MacBook Pro workstation |
Enable modules hierarchically per-user:
hostUsers.myuser.modules = {
home.browsers.enable = true; # enables all browsers
home.core.enable = true; # enables htop, shell-utils
home.shell.enable = true; # enables zsh, tmux
home.editors.neovim.enable = true; # specific module
};| Path | Modules |
|---|---|
home.core |
htop, shell-utils (bat, fzf, wget, direnv) |
home.shell |
zsh with oh-my-zsh, zplug, tmux |
home.terminal |
ghostty |
home.browsers |
chromium, vivaldi |
home.editors |
cursor, neovim (nixvim) |
home.desktop |
rofi, dunst, brightnessctl, pamixer, wayland-utils |
home.media |
vlc, spotify |
home.messengers |
telegram |
home.games |
steam |
home.security |
keepassxc, bitwarden |
home.downloads |
qbittorrent |
home.virtualisation |
docker |
home.utils |
raycast (darwin), macfuse (darwin) |
MIT License - see LICENSE for details.