This file provides context for any AI agent working with this NixOS flake configuration.
⚠️ AGENT SAFETY RULE: NEVER runnixos-rebuild switch,sudo nixos-rebuild,nix run .#<host>, or any command that activates/bootstraps a system configuration. Only use read-only validation commands:nix flake check,nix-agent_check,nix-agent_diff, andnix-agent_build. Breaking this rule can unexpectedly modify the host system.
For anything related to the den framework, always consult:
- https://github.com/vic/den/blob/main/AGENTS_EXAMPLE.md — comprehensive AI agent guide covering aspects, context pipeline, batteries, parametric dispatch, schema, and all den APIs. Read this before generating any den configuration.
- https://github.com/denful/den — source repository for looking up option definitions, battery implementations, CI test examples (
tree/main/templates/ci/modules/features/), and documentation (tree/main/docs/src/content/docs/).
When a nix flake update causes unexpected package removals, evaluation errors, or broken behaviour related to Den:
- Check release notes first — fetch
https://github.com/denful/den/releasesto find breaking changes between the old and new version. Den uses semantic versioning; minor bumps can include breaking API changes. - Read the docs — fetch
https://github.com/denful/den/tree/main/docs/src/content/docs/to understand the current API before touching any aspect definitions. Do not assume the API is the same as in your training data. - Cross-reference CI tests — the canonical source of truth for working patterns is
https://github.com/denful/den/tree/main/templates/ci/modules/features/. If a pattern isn't reflected in a passing CI test, treat it as unreliable. - Prefer CI test patterns over templates or docs examples — templates (e.g.
igloo.nix) may be illustrative rather than exhaustively tested. A pattern that appears in adenTestblock and is in the CI test suite is guaranteed to work for that version. - Do not deep-dive internal Nix source files until the docs and CI tests are exhausted — start with documentation and tests; resort to reading
nix/lib/source only if those don't resolve the issue.
This is a NixOS flake configuration for two hosts (void, voidframe) using the den framework with flake-parts and import-tree.
All .nix files under modules/ are automatically imported — no manual import wiring is needed. Adding a new file to any subdirectory of modules/ makes it available immediately after rebuild.
# flake.nix
inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)Configuration is split into independent aspect modules using the den framework. Each module defines den.aspects.<name> with optional nixos and homeManager sections:
# modules/category/example.nix
{ den, ... }:
{
den.aspects.example = {
nixos = { pkgs, ... }: {
services.example.enable = true;
};
homeManager = { pkgs, ... }: {
programs.example.enable = true;
};
};
}The outer { den, inputs, ... }: lambda is a flake-parts module.
The nixos/homeManager values are standard NixOS/HM modules.
Aspects can receive host and user context via the outer aspect lambda:
{ den, ... }:
{
den.aspects.example =
{ host, user, ... }:
{
nixos = { pkgs, lib, ... }: {
# host.hostName, host.monitors, host.xRes, host.isMultiMonitor or false,
# host.isLaptop or false, host.isGaming or false, host.gpuPciDev, etc.
systemd.services.greetd = lib.mkIf (host.isMultiMonitor or false) {
preStart = "${pkgs.fbset}/bin/fbset -xres ${host.xRes} -yres ${host.yRes}";
};
};
homeManager = { ... }: {
# user.userName, user.homeDirectory
home.file."hello".text = "hello ${user.userName}";
};
};
}Critical:
hostanduserare only available in the outer aspect lambda, NOT insidenixos/homeManagermodule args. They are captured via Nix lexical scoping. Usehost.attr or false/host.attr or ""for attributes that may not exist on all hosts.
User aspects can conditionally include other aspects based on host context:
{ den, lib, ... }:
{
den.aspects.example =
{ host, ... }:
{
includes = [
# always included
den.aspects.steam
den.aspects.mangohud
]
++ lib.optionals (host.isGaming or false) [
# only on gaming hosts
den.aspects.deadlock
den.aspects.wow
];
};
}├── flake.nix # Auto-generated by flake-file — do not edit directly
├── flake.lock # Lock file — updated via `nix flake update`
├── .sops.yaml # SOPS age encryption configuration
├── treefmt.toml # Tree-wide formatter configuration
├── AGENTS.md # AI agent context and instructions
├── TODO.md # Scratchpad / TODO list
├── hosts/ # Hardware-generated configs (nixos-generate-config)
│ ├── void/hardware-configuration.nix
│ └── voidframe/hardware-configuration.nix
├── devflakes/ # Development shell flakes (rust, go, zig, cpp, …)
├── snippets/ # Ad-hoc Nix snippets
├── modules/
│ ├── flake-inputs.nix # Flake input declarations — edit here, then run `nix run .#write-flake`
│ ├── den.nix # Bootstraps den, den defaults: stateVersion, HM config, sharedModules, user shell, hostname
│ ├── nh.nix # Exposes nix run .#<host> flake apps (builds with nh)
│ ├── hosts.nix # Declares hosts and their attributes
│ ├── system/ # OS-level aspects (boot, locale, networking, systemd, packages, users)
│ ├── hardware/ # Hardware aspects (bluetooth, kernel, udev, print, streamcontroller, usb)
│ ├── security/ # Security aspects (sops, pcscd, gnome-keyring, ly, noctalia-greeter, polkit)
│ ├── desktop/ # Desktop aspects (hyprland, stylix, noctalia, flatpak, fonts, gtk, xdg, satty, clipboard, cursor, environment, firefox, thunar)
│ │ └── hypr/ # Hyprland sub-aspect (hyprland.nix)
│ ├── shell/ # Shell aspects (zsh, bat, btop, direnv, delta, fastfetch, fzf, git, jj, jq, just, kitty, lazygit, lsd, mcp, nh, nix, opencode, payrespects, pure, tealdeer, yazi, zoxide)
│ ├── gaming/ # Gaming aspects (steam, mangohud, deadlock, wow)
│ ├── media/ # Media aspects (mpv, obs-studio, spicetify, ananicy, cava, easyeffects, pics, pipewire, network-drives)
│ ├── communication/ # Communication aspects (email, discord)
│ ├── home/ # Home-manager aspects (common, files, packages)
│ ├── ide/ # Editor aspects (nvim)
│ ├── nix/ # Nix daemon settings and overlays
│ ├── hosts/
│ │ ├── void/default.nix # void host aspect: system-only includes + hardware config
│ │ └── voidframe/default.nix # voidframe host aspect: same pattern
│ └── users/
│ └── neonvoid/neonvoid.nix # User aspect: all desktop/shell/app includes
hosts/ # Hardware-generated configs only
├── void/hardware-configuration.nix
└── voidframe/hardware-configuration.nix
assets/ # Static dotfiles, scripts, theme assets
secrets/ # SOPS-encrypted secrets
modules/hosts.nix — declares all hosts with freeform attributes including structured monitors, audio, network objects:
{ den, ... }:
let
neonvoid = {
gitName = "neonvoidx";
gitEmail = "me@neonvoid.dev";
emailName = "neonvoidx";
emailAddress = "me@neonvoid.dev";
};
timezone = "America/New_York";
in
{
den.hosts.x86_64-linux = {
void = {
users.neonvoid = neonvoid;
monitors = {
main = { name = "DP-2"; mode = "3440x1440@143.92"; scale = 1.0; primary = true; ... };
secondary = { name = "DP-3"; mode = "3440x1440@143.92"; ... };
portrait = { name = "HDMI-A-1"; mode = "2560x1440@59.95"; transform = 1; ... };
};
isMultiMonitor = true;
xRes = "3440";
yRes = "1440";
gpuPciDev = "0000:03:00.0";
gpuPciAudioDev = "0000:03:00.1";
gpuVendorDeviceId = "1002:7550";
audio = {
disabledNodes = [ ... ];
defaultMic = "alsa_input.usb-...";
defaultSpeaker = "alsa_output.usb-...";
bluetoothCard = "bluez_card...";
};
network = {
dns = [ "192.168.86.7" "192.168.86.8" ];
interface = "eth0";
mac = "9c:6b:00:98:96:96";
ip = "192.168.86.20";
prefixLength = 24;
gateway = "192.168.86.1";
};
nasIp = "192.168.86.6";
printerUri = "ipps://192.168.86.186/ipp/print";
greeting = "The Void";
timezone = timezone;
isGaming = true;
};
voidframe = {
users.neonvoid = neonvoid;
monitors.builtin = {
name = "eDP-1"; mode = "2880x1920@120";
scale = 1.33333; primary = true;
};
isLaptop = true;
xRes = "2880";
yRes = "1920";
network.wireless = true;
greeting = "Void Frame";
timezone = timezone;
isGaming = false;
};
};
}modules/hosts/<name>/default.nix — host aspect with system-only includes and hardware config:
{ den, inputs, ... }:
{
den.aspects.void = {
includes = [
# Core system
den.aspects.boot
den.aspects.locale
den.aspects.networking
den.aspects.systemd
den.aspects.users
den.aspects.overlays
den.aspects.nixsettings
# Hardware
den.aspects.bluetooth
den.aspects.kernel
den.aspects.print
den.aspects.udev
# Security
den.aspects.sops
den.aspects.pcscd
den.aspects.noctalia-greeter
den.aspects.polkit
# Services
den.aspects.ananicy
den.aspects.networkdrives
# System packages
den.aspects.systempackages
];
nixos = { lib, pkgs, config, ... }: {
imports = [ (inputs.self + "/hosts/void/hardware-configuration.nix") ];
# boot (limine with secure boot, amdgpu kernel modules, zen kernel, kernel params)
# hardware.amdgpu, hardware.steam-hardware, static IP networking
};
};
}Den auto-generates nixosConfigurations.void from hosts.nix — no flake-parts.nix needed.
provides.to-users: A host aspect can push additional includes or config to all of its users viaprovides.to-users(a thin shim over den'sdeliverprimitive). This is useful for host-specific features (e.g., gaming titles only relevant on the desktop) without polluting the shared user aspect. The inner value is a standard aspect lambda.
Hosts:
void— Desktop (AMD Ryzen 9 9950X, RX 9070 XT, 3x monitors: 2×3440×1440 + 2560×1440 portrait)voidframe— Framework laptop (AMD Ryzen 7 7840U, 2880×1920)
modules/users/neonvoid/neonvoid.nix — user aspect with all desktop/shell/app includes and host-conditional gaming aspects:
{ den, lib, ... }:
{
den.aspects.neonvoid =
{ host, ... }:
{
includes = [
# Shell tools
den.aspects.bat
den.aspects.btop
den.aspects.direnv
den.aspects.delta
den.aspects.fastfetch
den.aspects.fzf
den.aspects.git
den.aspects.jj
den.aspects.jq
den.aspects.just
den.aspects.kitty
den.aspects.lazygit
den.aspects.lsd
den.aspects.mcp
den.aspects.nh
den.aspects.nix-index
den.aspects.nvim
den.aspects.opencode
den.aspects.payrespects
den.aspects.pure
den.aspects.tealdeer
den.aspects.yazi
den.aspects.zoxide
den.aspects.zsh
# Desktop
den.aspects.de
den.aspects.fonts
den.aspects.xdg
den.aspects.stylix
den.aspects.noctalia
den.aspects.flatpak
den.aspects.clipboard
den.aspects.cursor
den.aspects.firefox
den.aspects.gtk
den.aspects.hyprland
den.aspects.satty
den.aspects.thunar
# Services (user-level)
den.aspects.gnomekeyring
den.aspects.pipewire
den.aspects.streamcontroller
den.aspects.usb
# Home
den.aspects.common
den.aspects.files
den.aspects.packages
# Media
den.aspects.cava
den.aspects.easyeffects
den.aspects.mpv
den.aspects.obsstudio
den.aspects.pics
den.aspects.spicetify
# Gaming
den.aspects.steam
den.aspects.mangohud
# Communication
den.aspects.discord
den.aspects.email
]
++ lib.optionals (host.isGaming or false) [
# Gaming — gated via host.isGaming
den.aspects.deadlock
den.aspects.wow
];
nixos = { ... }: {
users.users.neonvoid = {
description = "neonvoid";
extraGroups = [ "networkmanager" "audio" "video" "input" "libvirtd" "dialout" ];
};
};
};
}Critical: Den automatically applies the user aspect — do NOT add
den.aspects.neonvoidto host includes. Adding it to both causes double application of all homeManager configs.
Rule: Host includes = system-only aspects. User includes = all user-facing/desktop aspects. The
nixossections of user-included aspects still apply to the system.
Note:
isNormalUser, shell, and wheel group are handled automatically byden._.define-user,den._.user-shell, andden._.primary-userinmodules/den.nix. The usernixossection only needs extra groups or overrides.
modules/den.nix centralises shared configuration applied to every host:
den.default.nixos.system.stateVersionandden.default.homeManager.home.stateVersion— set to"26.11"den.default.nixos.home-manager—useGlobalPkgs,useUserPackages,backupFileExtension,backupCommand(removes old backup before backing up),sharedModules(spicetify-nix, nix-index-database, noctalia)den.default.includes—den._.home-manager,den._.define-user,den._.primary-user,den._.user-shell "zsh",den._.inputs',den._.self',den._.hostname- Imports
dendriticfrom bothflake-fileanddenfor the den schema den.schema.user.classes = [ "homeManager" ]
flake.nix is auto-generated by
flake-file. Never edit it directly. To add or change an input, editmodules/flake-inputs.nixthen runnix run .#write-flake. To update locked versions, runnix flake updateas normal.
| Input | Purpose |
|---|---|
den |
Den framework (v0.18.0) — auto-generates nixosConfigurations, wires HM, provides context |
nixpkgs |
NixOS unstable |
home-manager |
User environment management |
hyprland |
Wayland compositor |
stylix |
System-wide theming (base16, GTK, Qt, fonts) |
sops-nix |
Secrets management (age encryption) |
nixcord |
Declarative Discord client config (Discord + Equicord + OpenASAR) |
noctalia |
Desktop shell bar/launcher/lockscreen |
spicetify-nix |
Spotify theming |
nix-index-database |
Fast nix-locate lookups |
nix-versions |
Version tracking for nix commands |
nvim-config |
Neovim config (neonvoidx/nvim) |
neonmono |
Custom monospace font (neonvoidx/NeonMono) |
scopebuddy |
ScopeBuddy driver |
eldritch-cursors |
Eldritch theme cursors |
flake-file |
Regenerates flake.nix from flake-inputs.nix |
- Module file names: kebab-case (
desktop-environment.nix,system-packages.nix) - Aspect names: match the file name (
den.aspects."desktop-environment"). Shorthand names used:deforenvironment.nix. - Host names: lowercase (
void,voidframe) - User:
neonvoid(lowercase in description) _data/directories: hold split-out data excluded from import-tree (e.g.,hyprland/_data/keybindings.nix)- Host-specific conditionals: use
host.attr or falsein the outer lambda,lib.optionalsfor conditional includes,osConfig.fileSystems ? "/games"for filesystem checks in HM modules, orconfig.networking.hostName == "void"inside nixos modules - Styling/colors: base16 palette via stylix
- Secrets: SOPS age-encrypted in
secrets/, decrypted to/run/secrets/at boot - Git tracking: new files must be
git add-ed before rebuilding (Nix only evaluates git-tracked files) - Documentation: keep
AGENTS.mdandREADME.mdup to date when making changes — add new aspects, hosts, users, conventions, and structural changes to both files
When updating modules/desktop/noctalia.nix from a noctalia TOML config export, follow these rules:
- Read
/home/neonvoid/.local/state/noctalia/settings.toml— the TOML config exported from noctalia's UI. - Check
git diff(uncommitted changes) on that file to see what config was intentionally changed vs. committed state. - Use the uncommitted diff as reference on what to port — only port intentional config changes, not runtime state.
- All
[section]and[section.subsection]map to nested nix attrsets - TOML arrays become nix lists
- TOML inline tables become nix attrsets
- Use
host.monitorsvariables (mainName,secondaryName,portraitName,builtinName) instead of hardcoded monitor names - Use
${homeDir}instead of/home/neonvoid
- Wallpaper sections —
wallpaper.default,wallpaper.last,wallpaper.monitors.*(runtime state, not config) dock— not used in nix config- Shell paths — keep nix-style paths (
${homeDir}/.nix-profile/bin/...) rather than TOML's/etc/profiles/per-user/... - Any TOML key that is purely runtime state (e.g. last wallpaper path, active state)
- New plugins — compare
plugins.enabledlists - New plugin_settings — compare
plugin_settingssections - New widgets — compare
widget.*sections - Bar layout changes — compare
bar.main.start,bar.main.center,bar.main.endwidget lists - OSD settings — compare
osd.*(e.g.kinds.media) - Widget property additions — compare individual widget settings (e.g.
input_devicesonwidget.cat)
- Create
modules/<category>/<name>.nix— import-tree picks it up automatically - Add
den.aspects.<name>to the user's includes (modules/users/neonvoid/neonvoid.nix) for user-facing features, or to the host's includes (modules/hosts/<hostname>/default.nix) for system-only features - Validate:
nix flake checkornix-agent_check --level dry-build
- Add to
modules/hosts.nixunderden.hosts.x86_64-linux - Create
modules/hosts/<hostname>/default.nixwithden.aspects.<hostname> - Add
hosts/<hostname>/hardware-configuration.nix
- Create
modules/users/<username>/<username>.nixwithden.aspects.<username> - Add
users.<username> = {}to the relevant host entry inmodules/hosts.nix
When updating modules/desktop/noctalia.nix from a noctalia TOML config export, follow these rules:
- All
[section]and[section.subsection]map to nested nix attrsets - TOML arrays become nix lists
- TOML inline tables become nix attrsets
- Use
host.monitorsvariables (mainName,secondaryName,portraitName,builtinName) instead of hardcoded monitor names - Use
${homeDir}instead of/home/neonvoid
- Wallpaper sections —
wallpaper.default,wallpaper.last,wallpaper.monitors.*(runtime state, not config) dock— not used in nix config- Shell paths — keep nix-style paths (
${homeDir}/.nix-profile/bin/...) rather than TOML's/etc/profiles/per-user/... - Any TOML key that is purely runtime state (e.g. last wallpaper path, active state)
- New plugins — compare
plugins.enabledlists - New plugin_settings — compare
plugin_settingssections - New widgets — compare
widget.*sections - Bar layout changes — compare
bar.main.start,bar.main.center,bar.main.endwidget lists - OSD settings — compare
osd.*(e.g.kinds.media) - Widget property additions — compare individual widget settings (e.g.
input_devicesonwidget.cat)
# Validate configuration (lint, flake check, dry build)
nix flake check
nix-agent_check --level lint
nix-agent_check --level dry-build
# Build the closure without activating it
nix-agent_build
# Diff packages against the running system
nix-agent_diff
# Update flake inputs
nix flake update
# Regenerate flake.nix after changing flake-inputs.nix
nix run .#write-flake
# Check which packages are available
nix search nixpkgs <package>
# Enter a dev shell with all inputs
nix develop