Skip to content

Latest commit

 

History

History
564 lines (447 loc) · 21.4 KB

File metadata and controls

564 lines (447 loc) · 21.4 KB

NixOS Flake — Agent Instructions

This file provides context for any AI agent working with this NixOS flake configuration.

⚠️ AGENT SAFETY RULE: NEVER run nixos-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, and nix-agent_build. Breaking this rule can unexpectedly modify the host system.


Den Framework Reference

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/).

Diagnosing Den Issues After a Flake Update

When a nix flake update causes unexpected package removals, evaluation errors, or broken behaviour related to Den:

  1. Check release notes first — fetch https://github.com/denful/den/releases to find breaking changes between the old and new version. Den uses semantic versioning; minor bumps can include breaking API changes.
  2. 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.
  3. 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.
  4. 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 a denTest block and is in the CI test suite is guaranteed to work for that version.
  5. 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.


Core Architecture

Auto-Discovery via 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)

Den Framework

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.

Host & User Context

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: host and user are only available in the outer aspect lambda, NOT inside nixos/homeManager module args. They are captured via Nix lexical scoping. Use host.attr or false / host.attr or "" for attributes that may not exist on all hosts.

Conditional Includes via lib.optionals

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
      ];
    };
}

Directory Structure

├── 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

Host Definitions

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 via provides.to-users (a thin shim over den's deliver primitive). 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)

User Definitions

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.neonvoid to 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 nixos sections of user-included aspects still apply to the system.

Note: isNormalUser, shell, and wheel group are handled automatically by den._.define-user, den._.user-shell, and den._.primary-user in modules/den.nix. The user nixos section only needs extra groups or overrides.


den.nix — Defaults & HM Integration

modules/den.nix centralises shared configuration applied to every host:

  • den.default.nixos.system.stateVersion and den.default.homeManager.home.stateVersion — set to "26.11"
  • den.default.nixos.home-manageruseGlobalPkgs, useUserPackages, backupFileExtension, backupCommand (removes old backup before backing up), sharedModules (spicetify-nix, nix-index-database, noctalia)
  • den.default.includesden._.home-manager, den._.define-user, den._.primary-user, den._.user-shell "zsh", den._.inputs', den._.self', den._.hostname
  • Imports dendritic from both flake-file and den for the den schema
  • den.schema.user.classes = [ "homeManager" ]

Key Inputs

flake.nix is auto-generated by flake-file. Never edit it directly. To add or change an input, edit modules/flake-inputs.nix then run nix run .#write-flake. To update locked versions, run nix flake update as 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

Conventions

  • Module file names: kebab-case (desktop-environment.nix, system-packages.nix)
  • Aspect names: match the file name (den.aspects."desktop-environment"). Shorthand names used: de for environment.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 false in the outer lambda, lib.optionals for conditional includes, osConfig.fileSystems ? "/games" for filesystem checks in HM modules, or config.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.md and README.md up to date when making changes — add new aspects, hosts, users, conventions, and structural changes to both files

Noctalia TOML → Nix Conversion

When updating modules/desktop/noctalia.nix from a noctalia TOML config export, follow these rules:

Source of truth

  • 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.

What to convert

  • All [section] and [section.subsection] map to nested nix attrsets
  • TOML arrays become nix lists
  • TOML inline tables become nix attrsets
  • Use host.monitors variables (mainName, secondaryName, portraitName, builtinName) instead of hardcoded monitor names
  • Use ${homeDir} instead of /home/neonvoid

What to ignore

  • Wallpaper sectionswallpaper.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)

What to check for differences

  • New plugins — compare plugins.enabled lists
  • New plugin_settings — compare plugin_settings sections
  • New widgets — compare widget.* sections
  • Bar layout changes — compare bar.main.start, bar.main.center, bar.main.end widget lists
  • OSD settings — compare osd.* (e.g. kinds.media)
  • Widget property additions — compare individual widget settings (e.g. input_devices on widget.cat)

Adding New Aspects

  1. Create modules/<category>/<name>.nix — import-tree picks it up automatically
  2. 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
  3. Validate: nix flake check or nix-agent_check --level dry-build

Adding a New Host

  1. Add to modules/hosts.nix under den.hosts.x86_64-linux
  2. Create modules/hosts/<hostname>/default.nix with den.aspects.<hostname>
  3. Add hosts/<hostname>/hardware-configuration.nix

Adding a New User

  1. Create modules/users/<username>/<username>.nix with den.aspects.<username>
  2. Add users.<username> = {} to the relevant host entry in modules/hosts.nix

Noctalia TOML → Nix Conversion

When updating modules/desktop/noctalia.nix from a noctalia TOML config export, follow these rules:

What to convert

  • All [section] and [section.subsection] map to nested nix attrsets
  • TOML arrays become nix lists
  • TOML inline tables become nix attrsets
  • Use host.monitors variables (mainName, secondaryName, portraitName, builtinName) instead of hardcoded monitor names
  • Use ${homeDir} instead of /home/neonvoid

What to ignore

  • Wallpaper sectionswallpaper.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)

What to check for differences

  • New plugins — compare plugins.enabled lists
  • New plugin_settings — compare plugin_settings sections
  • New widgets — compare widget.* sections
  • Bar layout changes — compare bar.main.start, bar.main.center, bar.main.end widget lists
  • OSD settings — compare osd.* (e.g. kinds.media)
  • Widget property additions — compare individual widget settings (e.g. input_devices on widget.cat)

Useful Commands

# 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