NixOS desktop config (Hyprland + Plasma 6) -- custom 10-group Neovim syntax highlighting and Ayu Dark theming across the full stack
NixOS Desktop Config
Modular NixOS configuration with Hyprland (UWSM) + Plasma 6 fallback, SDDM display manager.
Structure
nixos/ # NixOS system config (flake-based)
flake.nix # inputs: nixpkgs-unstable, home-manager, agenix, lanzaboote
host.nix # machine-specific constants (edit for new PCs)
configuration.nix # entry point
hardware.nix # boot, kernel, NVIDIA, CPU, bluetooth
hardware-configuration.nix # auto-generated (nixos-generate-config)
desktop.nix # SDDM, Hyprland, Plasma, portals, audio, fonts
security.nix # SSH, PAM, gnome-keyring, agenix
programs.nix # system packages, steam, docker, flatpak
auto-upgrade.nix # nightly check for upstream updates + notification
nordvpn.nix # NordVPN daemon + GUI (FHS-wrapped from .deb)
overlays/cli-tools.nix # br built from source (upstream flake is broken)
home/ # home-manager modules
scripts/
install-ai-tools.sh # native install of claude-code + codex to ~/.local/bin
add-secret.sh # add and wire up a new agenix secret
config/ # dotfiles (most symlinked by link.sh)
nvim/ # Neovim (NvChad), symlinked
hypr/ # Hyprland user config, symlinked
kitty/ # terminal, symlinked
hyprpanel/ # status bar, symlinked
rofi/ # app launcher, symlinked
p10k/ # Powerlevel10k prompt, symlinked
clangd/ # clangd config, symlinked
gtk-3.0/, gtk-4.0/ # GTK accent overrides (home-manager)
mc/ # Midnight Commander (home-manager)
kde/ # KDE color scheme (home-manager)
skills/ # AI agent skills (see skills/README.md)
wallpapers/ # wallpaper images (gitignored)
Quick Start
0. Clone
git clone https://github.com/Lallapallooza/simple_nix.git
cd simple_nix
1. Edit nixos/host.nix
All machine-specific values live here:
{
username = "my_username";
# homeDir is derived as /home/${username} in flake.nix
hostname = "nixos";
timezone = "Europe/Dublin";
defaultLocale = "en_US.UTF-8";
regionalLocale = "en_IE.UTF-8";
tmpfsSize = "16G"; # ~half of RAM
steamScaling = "1.666667"; # match your monitor scale
cursorSize = 24; # 16@1x, 24@1.5x, 32@2x
nvidia = true; # false for AMD/Intel GPU
repoDir = "/home/myusername/code/simplenix"; # local clone path (for update checks)
autoUpgrade = true; # nightly check for upstream updates (notifies, doesn't rebuild)
}
2. Edit config/hypr/user.conf
Hyprland config is plain text (not Nix), edit manually:
# Monitor
monitor = DP-4, 3840x2160@240, 0x0, 1.666667
^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^
connector resolution+hz scale factor
Scaling (must match your monitor scale)
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
env = GDK_SCALE,2
env = GDKDPISCALE,0.8333
NVIDIA-specific (set to false/0 on AMD/Intel)
xwayland { forcezeroscaling = true }
cursor { nohardwarecursors = true }
3. Build
./install.sh
Relog (SDDM) to pick up the new Hyprland session.
Known issues
- Telegram steals focus on new messages -- Telegram's "Draw attention to the window" feature uses Wayland activation, which Hyprland treats as a focus request. Go to Telegram -> Settings -> Notifications -> disable Draw attention to the window. See hyprwm/Hyprland#9186.
Deployment
- home-manager deploys mc and kde as nix store symlinks (
./install.shto apply) - link.sh symlinks everything else for live editing (no rebuild needed)
Dev Environment
- nix-ld enabled -- pip wheels, npm native modules, and other binary packages just work
- direnv for per-project environments -- drop a
.envrcand tools auto-activate oncd:
# Python venv
echo 'source .venv/bin/activate' > .envrc && direnv allow
# Nix shell echo 'use nix' > .envrc && direnv allow
- Python: uv, basedpyright + ruff, Python 3.13. PyTorch wheels bundle their own CUDA runtime --
uv pip install torchworks with nix-ld - C/C++: clang, clangd, cmake. NixOS pitfall -- clangd can't find standard headers without this in CMakeLists.txt:
set(CMAKEEXPORTCOMPILE_COMMANDS ON) set(CMAKECXXSTANDARDINCLUDEDIRECTORIES ${CMAKECXXIMPLICITINCLUDEDIRECTORIES}) Then: ln -s build/compile_commands.json . - Rust: rustc, cargo, rust-analyzer
- Go: go, gopls
- JVM: JDK, Kotlin, kotlin-language-server
- Neovim: NvChad-based, LSPs from nix (clangd, basedpyright, ruff, rust-analyzer, tsls, bashls), conform.nvim for formatting. See
config/nvim/CHEATSHEET.md - AI coding tools: claude-code and codex install natively via
scripts/install-ai-tools.sh(they update too often for OS rebuilds) - AMD uProf (optional, AMD CPUs only) -- microarchitectural profiler: IBS sampling, Zen PMU counters, cache/mem/branch/TLB events, power timechart. See AMD uProf setup
- NVIDIA Nsight Graphics (optional, NVIDIA GPUs only) -- headless frame debugger and GPU profiler CLI for Vulkan/OpenGL/DirectX. See Nsight Graphics setup
Theme
Ayu Dark everywhere. One palette, every app.
Stock Ayu Dark looks great out of the box but collapses several semantic roles to the same color. Parameters and constants/numbers are both purple (#D2A6FF) -- you can't tell a function argument from a numeric literal. Properties and library functions share coral (#F07178). The two blues (#39BAE6 for built-in types and #59C2FF for user types) are hard to tell apart in practice.
This config fixes those collisions with a 10-group system. Parameters stay purple, but constants get their own muted gold (#C4B070). Properties move to mint (#80DDB8). Self/builtins get cyan (#39BAE6), clearly distinct from type blue (#59C2FF). The foreground is dimmed to #B8B5AC (stock is brighter #BFBDB6) so accent colors pop above the text ground level. Constants and operators are pushed below foreground brightness -- they recede so the important tokens stand out.
| Role | Hex | Used for | |------|-----|----------| | Background | #0B0E14 | kitty, hyprlock, hyprpanel, KDE, rofi | | Surface | #14171D | rofi bg-alt, hyprpanel cards, KDE Window bg | | Border | #1A1D23 | Hyprland inactive border, hyprpanel borders | | Foreground | #B8B5AC | kitty, hyprlock, hyprpanel, KDE, rofi | | Dimmed | #3D4046 | kitty bright-black, disabled states | | Accent | #FF8F40 | Hyprland borders, hyprlock, hyprpanel, kitty cursor, rofi, GTK, KDE | | Red | #F07178 | errors, destructive actions | | Green | #AAD94C | strings, success states | | Yellow | #E6C54C | warnings, functions (neovim) | | Blue | #59C2FF | types, links, info | | Purple | #D2A6FF | parameters, lifetimes | | Cyan | #39BAE6 | self/builtins | | Mint | #80DDB8 | properties, regex, special chars | | Orange | #FF7733 | keywords (neovim anchor color) |
Neovim: 10-group syntax highlighting
- Keywords
#FF7733orange -- the anchor color everything else is tuned against - Functions
#E6C54Cgold -- warm but 22 degrees from keyword orange on the hue wheel - Strings
#AAD94Cgreen -- standard, distinct from everything - Types+Modules
#59C2FFblue -- structs, classes, imports, tags - Constants
#C4B070muted gold -- intentionally dimmer than fg so they recede - Self/Builtins
#39BAE6cyan --self,this,init - Properties
#80DDB8mint -- struct fields, object members - Parameters
#D2A6FFpurple -- function arguments, lifetimes - Decorators
#E6C08Abeige --@decorator,#[attr], macros - Operators
#CC7832dark orange -- quiet scaffolding, doesn't compete with keywords
- No italic anywhere -- keeps monospace grid clean
- CVD-aware -- tested for mild red-green color vision deficiency; no meaning carried by red-green contrast alone
- LSP semantic tokens enabled -- treesitter handles syntax, LSP adds type-level precision; empty
@lsp.type.variable = {}lets treesitter's more specific groups show through - Foreground dimmed to
#B8B5AC-- stock is brighter#BFBDB6; dimming gives accent colors more contrast
Desktop theming
Applied via:
- kitty -- 16 terminal colors + cursor/selection in
kitty.conf - KDE -- full Ayu Dark color scheme in
kdeglobals(deployed by home-manager) - GTK 3/4 -- accent, destructive, success, warning, error overrides in
gtk.css - rofi -- custom
ayu-dark.rasitheme - hyprpanel -- custom
config.jsonwith Ayu Dark palette - hyprlock -- blurred background with Ayu Dark text/input colors
- Hyprland -- border colors use accent orange
AMD uProf (optional)
AMD uProf isn't in nixpkgs -- AMD gates every download behind a EULA form, so no fetchurl is possible. The overlay at nixos/overlays/amduprof.nix handles it via requireFile: you download the tarball once, add it to the Nix store, set a flag, rebuild.
Gated by amduprof in host.nix. Leave it false (default) and install.sh works on any machine without the tarball.
One-time setup
# 1. Download AMDuProfLinuxx64_<version>.tar.bz2 (~300 MB) from:
https://www.amd.com/en/developer/uprof.html
(The version pinned in the overlay is 5.2.606 -- bump uprofVersion
there if you grab a newer release.)
2. Compute its hash and paste into uprofHash in
nixos/overlays/amduprof.nix:
nix hash file ~/Downloads/AMDuProfLinuxx64_5.2.606.tar.bz2
3. Add the tarball to the Nix store:
nix store add-file ~/Downloads/AMDuProfLinuxx64_5.2.606.tar.bz2
4. Enable the flag in nixos/host.nix:
amduprof = true;
5. Rebuild
./install.sh
Afterwards AMDuProfCLI, AMDuProfPcm, and AMDuProfCfg are on PATH. Quick check:
AMDuProfCLI info # system/CPU info
AMDuProfCLI collect --config tbp -o /tmp/uprof ./myapp # sample a binary
AMDuProfCLI report -i /tmp/uprof/AMDuProf-* # text report
No msr kernel-module changes are needed for user-space profiling on the default NixOS sysctls. If a specific counter errors with "Permission denied", run that single invocation with sudo -- CAPSYSADMIN bypasses the default paranoid level. If the wrapper complains about a missing .so, add the library to fhsDeps in the overlay and rebuild.
Instruction Based Sampling (IBS) and perfeventparanoid
IBS profile scopes require kernel.perfeventparanoid <= 1 for non-root users. NixOS defaults to 2, so AMDuProfCLI collect with an IBS config fails with:
ERROR: For non-root users, following perfeventparanoid values are valid
for Instruction Based Sampling:
<= 1 : for all Instruction Based Sampling profile scopes.
Temporary (resets on reboot):
sudo sysctl -w kernel.perfeventparanoid=1
Persistent, add to nixos/configuration.nix:
boot.kernel.sysctl."kernel.perfeventparanoid" = 1;
Check the current value with cat /proc/sys/kernel/perfeventparanoid. Alternatively, run AMDuProfCLI collect under sudo -- root bypasses the paranoid check entirely.
NVIDIA Nsight Graphics (optional)
Nsight Graphics isn't in nixpkgs (only nsightcompute and nsightsystems are). NVIDIA gates the download behind a developer login, so no fetchurl is possible. The overlay at nixos/overlays/nsight-graphics.nix handles it via requireFile: it extracts the Makeself installer's pkg/ tree and wraps the headless CLI in a buildFHSEnv. The GUI (ngfx-ui) is intentionally skipped.
Gated by nsightGraphics in host.nix. Leave it false (default) and install.sh works on any machine without the installer.
One-time setup
# 1. Download NVIDIANsightGraphics<version>-linuxx64.run (~450 MB) from:
https://developer.nvidia.com/nsight-graphics
(The version pinned in the overlay is 2026.2.0.26134 -- bump
nsightVersion there if you grab a newer release.)
2. Compute its hash and paste into nsightHash in
nixos/overlays/nsight-graphics.nix:
nix hash file ~/Downloads/NVIDIANsightGraphics2026.2.0.26134-linuxx64.run
3. Add the installer to the Nix store:
nix store add-file ~/Downloads/NVIDIANsightGraphics2026.2.0.26134-linuxx64.run
4. Enable the flag in nixos/host.nix:
nsightGraphics = true;
5. Rebuild
./install.sh
Afterwards ngfx, ngfx-capture, and ngfx-replay are on PATH. Quick check:
ngfx --version # tool version
ngfx --help-all # general + per-activity options
ngfx --activity "GPU Trace Profiler" --exe ./myapp --output-dir /tmp/trace
The NVIDIA driver libraries reach the sandbox through /run/opengl-driver, which buildFHSEnv mounts automatically. If the wrapper complains about a missing .so, add the library to fhsDeps in the overlay and rebuild.
Secure Boot (dual-boot with Windows)
NixOS boots through Secure Boot via Lanzaboote, which signs systemd-boot stubs with custom keys. Microsoft's keys are kept alongside for Windows compatibility.
After a fresh install or on a new machine:
# 1. Generate Secure Boot signing keys
sudo nix-shell -p sbctl --run "sbctl create-keys"
2. Build and sign (keys must exist before rebuild)
./install.sh
3. Verify all boot entries are signed (kernel-* unsigned is expected)
sudo nix-shell -p sbctl --run "sbctl verify"
Then configure the BIOS (ASRock Taichi X870E shown, other boards similar):
4. Reboot into BIOS (F2/Del), Advanced Mode (F6)
- Security > Secure Boot > Secure Boot Mode = Custom
- Clear Secure Boot Keys > Yes
- F10 save, boot back into NixOS
# 8. Enroll your keys + Microsoft's (--microsoft is mandatory for dual-boot)
sudo nix-shell -p sbctl --run "sbctl enroll-keys --microsoft"
9. Reboot into BIOS (F2/Del)
- Security > Secure Boot = Enabled
- F10 save
# 12. Verify -- should show "Secure Boot: enabled (user)"
bootctl status
To boot Windows: press F11 at POST for the boot device menu, select the Windows drive. BitLocker will ask for the recovery key once after key enrollment, then re-seals automatically.
AI Coding Tools
claude-code and codex release too often to package in nix (an OS rebuild per bump). They install natively to ~/.local/bin (already on PATH via home-manager):
./scripts/install-ai-tools.sh # install/update both
./scripts/install-ai-tools.sh codex # just one
claude-code uses Anthropic's official installer and self-updates afterwards. codex installs from npm (@openai/codex, vendors static musl binaries that run on NixOS unpatched) with --prefix ~/.local; rerun the script to update it.
br is built from source in nixos/overlays/cli-tools.nix because its upstream flake is broken.
The nightly update check (nixos/auto-upgrade.nix) is enabled by default (autoUpgrade = true in host.nix). It fetches origin/main and notifies you (shell login + desktop notification) if your local branch is behind. You rebuild manually with ./install.sh. Set to false to disable.
Secrets
Secrets are managed with agenix -- age-encrypted files committed to the repo. Ciphertext is safe to publish; only holders of the private key can decrypt.
Two decryption keys are configured in nixos/secrets/secrets.nix:
- Personal age key -- portable, stored in a password manager. Use this to bootstrap new machines.
- Machine host key -- per-machine SSH host key for unattended decryption at boot.
Using your own secrets
To use this config with your own secrets:
# 1. Generate a personal age key (store the private key in your password manager!)
age-keygen -o ~/.config/age/keys.txt
2. Get your machine's host key
cat /etc/ssh/sshhosted25519_key.pub
3. Edit nixos/secrets/secrets.nix -- replace both keys with yours
vitalii = "age1..."; <-- your age public key (from step 1)
nixos = "ssh-ed25519 AAAA..."; <-- your host key (from step 2)
4. Create your GitHub SSH key secret
cd nixos/secrets
agenix -e ided25519github.age -i ~/.config/age/keys.txt
(paste your GitHub SSH private key, save)
5. Build
./install.sh
Adding a new machine
# On the new machine:
1. Copy your personal age key from password manager to ~/.config/age/keys.txt
2. Add the new machine's host key to nixos/secrets/secrets.nix
cat /etc/ssh/sshhosted25519_key.pub
3. Re-encrypt all secrets for the new recipient (from any machine that can decrypt)
cd nixos/secrets && agenix -r -i ~/.config/age/keys.txt
4. Commit, push, then install on the new machine
./install.sh
Adding more secrets
# Interactive: prompts for the secret value (hidden input)
./scripts/add-secret.sh my-api-key
It will:
1. Encrypt the value to nixos/secrets/my-api-key.age
2. Add the entry to secrets.nix automatically
3. Wire it up in security.nix
Then apply
./install.sh
Wallpaper
Place a wallpaper image in wallpapers/wallpaper.png (gitignored). link.sh will symlink it to ~/Pictures/wallpaper.png, where hyprpaper picks it up.
To use a different image, replace the file or edit config/hypr/hyprpaper.conf.