- nixOS - An indubitably splendiferous configuration
Warning
The image above is very out of date. I'll update it when Ghostty blur lands in stable, but be aware a lot of changes have occurred since that picture was taken (whoops!)
A modular NixOS + Home Manager configuration which aims to, among other things, facilitate keyboard-centric navigation (every relevant option has an equivalent mouse control though as I'm also still learning)! It is easily extensible but comes with the following opinionated default setup:
- A Wayland display server running the niri scrollable-tiling compositor, with auto-login via greetd.
- Wayle as the unified desktop shell (handling bar, notifications, and the OSD).
- Vicinae as the app launcher (an open-source launcher inspired by Raycast).
- Ghostty with Zsh, Starship prompt, and Atuin history.
- Helix as a TUI editor and Zed as a GUI editor.
- Yazi as a TUI and Dolphin as a GUI file explorer.
- A fully declarative Zen Browser configuration.
- Automatic light/dark theme switching using Stylix and sunwait.
- Secret encryption via Agenix.
- And much, much more...
This is the configuration that started my NixOS journey, and it has stayed with me ever since. Therefore, there may be some anti-patterns, and non idiomatic ways of doing things in my configuration that go against how you should "Nix". However, the configuration has served me well over the more than a year straight that I have used Nix, and considering the fact that it has been the only distro I have not switched back to Windows after using, I am satisfied. Plus, the way I configured it at the start was with modularity in mind, so there is actually barely any technical debt when editing it - thanks past me :3!
Note
hardware-configuration.nix files are not committed to this repo (as per the .gitignore). However, they are themselves the exact file generated by Nix (down to the comments), so all you have to do is legitimately just copy over the new hardware-configuration.nix file with no changes at all. Configuration occurs in other files that import them, or other files which are imported - and in the case of the latter said files are committed to this repo.
| Hostname | Architecture | Graphics | Notes |
|---|---|---|---|
lyra |
x86_64-linux |
Intel | CachyOS kernel. |
| Username | Description | Default Shell | Desktop Environment |
|---|---|---|---|
ami |
Mah user | zsh |
Niri + Wayle + Vicinae + Ghostty + Zen Browser |
rescue |
In case of kaboom user | bash |
Fallback system user so none |
Important
"Users have a free 5 GB limit for open source projects and 20 Cachix Deploy agents." - Cachix Pricing.
Before we go more in depth, I want to first state that I have a personal cache setup on Cachix under skifli-nixos (yes it's free). This is extremely useful and I would suggest anyone reading this to also set one up (unless you have your own alternative already). 5 GB may not seem a lot but since in Cachix you can also configure upstream caches (i.e., they skip uploading store paths already present in any of them), 5 GB is plenty. Plus, you can pin specific paths to tell them not to delete (which they do based on a LRU policy if you are over 5 GB), and my pins are less than 4 GiB, so I have plenty of space!
I have a workflow setup under nix-build.yml that builds when I tell it to (which used to be on push but sometimes I don't want / need it to), and caches anything built locally (i.e., not pulled from caches), and it also makes sure to pin my main system paths! This has helped bring my rebuild times down to an average of 2-3 minutes, which compared to the 10+ it was before is quite an improvement :p. Of course, my system is a bit complex with e.g., multiple specialisations for light / dark theme, Home Manager, etc, which mean it won't be the fastest to rebuild - but if anyone has any other ideas I'm all ears :).
My configuration also uses some other GitHub Actions located in .github/workflows/:
flake-checker.yml: Runs weekly checks onflake.lockhealth.nix-build.yml: As aforementioned, this builds thelyrasystem (for both light and dark theme specialisations), pushes the results directly to Cachix, and pins the latest two revisions.nix-maid.yml: Runsstatix,deadnix, andalejandraon pushes/PRs, automatically opening a PR with the changes.update-lockfile.yml: Scheduled weekly run (Sundays at 00:00) that automatically updatesflake.lockand opens an automated PR.dependabot.yml: Make sure the GitHub Actions dependencies are up to date, weekly.
A major goal of this config is being able to control as close as possible to everything from the keyboard without needing to constantly move my hand for the mouse (this is also made harder by the fact I don't have half a finger)!
- Window & workspace level: Controlled via Niri (
Mod + H/J/K/Land arrow keys). - Standard GUI applications without Vim keys: Controlled via Kanata mouse emulation (
Spaceheld +E/S/D/Ffor movement,J/K/Mfor clicks). - Web browser navigation: Controlled in Zen Browser via the Vimium-FF extension (
f,F,d,u,gg,G). - Files, shell, and clipboard: Via Ghostty, Yazi, and custom shell commands (
copyl,blammo,$blammo_in). - Global search and launching: Handled via Vicinae (
Mod + D), Fuzzel, and custom layout scripts.
Configured in users/programs/keyboard/kanata.nix, with the default / current configuration (which is in users/ami/variables.nix) being in a Lenovo Calliope UK ISO layout:
- Activating mouse mode: Tap
Spaceto type a normal space. HoldSpace(longer than 250ms) to activate mouse mode. - Toggle lock: While in mouse mode, pressing
ntoggles mouse mode permanently ON so you don't have to keep holding space. Tapnagain to unlock. - Left hand (movement and modifiers):
E/S/D/F: Move cursor Up / Left / Down / Right (with smooth acceleration).A: Hold Shift (for shift-clicking or text selection).G: Hold Ctrl (for multi-selection).Q: Hold Super / Meta.T: Hold Alt.
- Right hand (clicks, scrolling, zoom):
J: Left click and drag (holdJwhile moving withE/S/D/Fto drag-select).K: Right click.M: Middle click.I/O: Scroll down / Scroll up.W/R: Scroll left / Scroll right.L: Hold for slow precision mode (movement set to 1px micro-steps).U/P: Zoom in / Zoom out (Ctrl+=/Ctrl+-macros).
Configured in users/programs/compositor/niri/binds.nix:
Focus and movement
| Keybinding | Action |
|---|---|
Mod + H / Mod + Left |
Focus column left |
Mod + L / Mod + Right |
Focus column right |
Mod + J / Mod + Down |
Focus window down (in a vertical column) |
Mod + K / Mod + Up |
Focus window up (in a vertical column) |
Mod + Ctrl + J / Down |
Focus workspace down |
Mod + Ctrl + K / Up |
Focus workspace up |
Mod + 1 / Mod + Alt + H |
Focus left monitor (HDMI-A-2) |
Mod + 2 / Mod + Alt + L |
Focus right monitor (DP-1) |
Mod + Shift + 1 .. 8 |
Jump straight to workspace 1 through 8 |
Mod + Tab |
Smart overview (centers active workspaces) |
Mod + Shift + Tab |
Standard overview toggle |
Mod + A |
Interactive window switcher menu (window-switcher.sh) |
Alt + Tab / Alt + Shift + Tab |
Next / previous recent window |
Alt + grave |
Focus next window on the current monitor |
Mod + Shift + H / Left |
Move column left |
Mod + Shift + L / Right |
Move column right |
Mod + Shift + J / Down |
Move window down in column |
Mod + Shift + K / Up |
Move window up in column |
Mod + Ctrl + Shift + J / Down |
Move column to workspace down |
Mod + Ctrl + Shift + K / Up |
Move column to workspace up |
Mod + Shift + Home / End |
Move column to first / last position |
Mod + Comma / Mod + Period |
Consume window into column / Expel window from column |
Mod + Alt + Shift + H/J/K/L |
Nudge floating window 10% in direction |
Window sizing and column controls
| Keybinding | Action |
|---|---|
Mod + W |
Toggle tabbed column view (stacks column vertically with tabs) |
Mod + Equal (+) |
Increase column width (+10%) |
Mod + Minus (-) |
Decrease column width (-10%) |
Mod + C |
Center column |
Mod + M |
Maximize column |
Mod + F11 |
Toggle fullscreen |
Mod + Shift + F11 |
Toggle windowed fullscreen |
Mod + O |
Toggle window floating |
Mod + R / Mod + Shift + R |
Cycle preset column widths (forward / back) |
Mod + Ctrl + R / Shift |
Cycle preset window heights (forward / back) |
App toggles (through find-or-make.sh)
The app toggle shortcuts will focus the application if already open, launch it if closed, or switch back to the previous window if the target is already focused (last one as per niri settings):
| Keybinding | Application |
|---|---|
Mod + D |
Vicinae launcher |
Mod + Return |
Ghostty terminal |
Mod + Shift + Return |
Centered floating terminal (floating-term.sh) |
Mod + F |
Dolphin file manager |
Mod + Shift + F |
Yazi file manager |
Mod + V |
Zed editor (zeditor) |
Mod + E |
Helix editor (hx) |
Mod + Shift + Z |
Zen Browser (zen-beta) |
Mod + Shift + A |
Anytype |
Mod + Shift + N |
Anki |
Mod + Shift + C |
Ferdium |
Mod + Shift + D |
TigerVNC / FreeRDP |
Mod + Shift + Y |
Affinity suite |
Ctrl + Shift + Escape |
Mission Center (system-monitor) |
Shift + Escape |
Btop |
Mod + N |
Todo and reminder manager (todo.sh) |
Mod + Q |
Close focused window |
Mod + Shift + Q |
Force kill current window (killcurrent) |
Mod + Ctrl + Q |
Interactive click-to-kill window (killclick) |
Layout preset scripts
| Keybinding | Script | Description |
|---|---|---|
Mod + Ctrl + 1 |
1-niri.sh |
Default layout: Zen, Anki, Anytype, Ferdium, and TigerVNC arranged across both monitors. |
Mod + Ctrl + 2 |
2-niri.sh |
NEA mode: Evince PDF, Zed editor, SyncTeX daemon, and Zen Browser. |
Mod + Ctrl + 3 |
3-niri.sh |
F1iS mode: Affinity suite, Zen, Anytype, and Ferdium. |
Mod + Ctrl + 4 |
4-niri.sh |
Anki focus mode: Stashes distraction apps into the scratchpad; places Anki on monitor 1, Zen and Anki Pomodoro on monitor 2. |
Configured using nirius:
| Keybinding | Action | Description |
|---|---|---|
Mod + P |
Toggle scratchpad | Parks or unparks the focused window into the background scratchpad. |
Mod + Shift + P |
Cycle scratchpad | Cycles through parked scratchpad windows one by one. |
Mod + Ctrl + P |
Toggle all scratchpad | Shows or hides all scratchpad windows at once. |
Mod + Alt + P |
List scratchpad | Sends a desktop notification listing all parked scratchpad windows. |
Mod + T |
Toggle mark | Tags/untags the focused window with a mark. |
Mod + Shift + T |
Focus marked | Jumps straight to your marked window from anywhere. |
Mod + Alt + T |
List marked | Sends a desktop notification listing all marked windows. |
Mod + Ctrl + F |
Toggle follow mode | Makes the focused window follow along when changing workspaces. |
Configured in users/programs/terminal-shell/zsh/initContent.sh:
copyl <file>: Converts any local path into afile:///URI and copies it to the Wayland clipboard usingwl-clipboard.blammoand$blammo_in: When selecting files in Yazi (Mod + Shift + F) and suspending (Ctrl + Z) or quitting (q), Yazi saves the selected paths into/tmp/blammo. The Zsh prompt automatically loads this into$blammo_inso you can do things likecopyl $blammo_in,ls -la $blammo_in, or run batch operations on the file selection.safe_reboot: System shutdown or restart is auto blocked with a notification if a screen recording is active, a NixOS rebuild is running, or urgent windows need attention.
Nix and rebuild aliases (nh)
I use nh nearly all the time instead of raw nixos-rebuild commands:
| Alias | Command(s) | Usage |
|---|---|---|
nsw |
nh os switch |
Normal switch for daily config changes |
nup |
nh os switch --update |
Updates all flake inputs and switches at once |
ntest |
nh os test |
Temporary switch (back to previous on reboot) |
nboot |
nh os boot |
Build and set as default for the next boot only |
ndry |
nh os switch --dry |
Dry run with inbuilt nvd package diff |
nask |
nh os switch --ask |
Shows visual package diff and prompts before switching |
nvm |
nh os build-vm |
QEMU sandbox build to test bigger changes |
nfc |
nix flake check --no-build |
Tests the flake syntax without building |
ncl |
nh clean all --keep 5 |
Garbage collects old generations while keeping 5 safe |
znsw |
z nixos && git pull && nh os switch |
Syncs repo changes and switches |
znup |
z nixos && git pull && nh os switch -u |
Syncs repo changes, updates flake inputs, and switches |
gfu |
git commit -m 'feat(flake.lock): update' ... |
Commit and push for updated lockfiles |
The configuration handles user scripts in two ways so adding another user shouldn't be too much of a hassle:
- Standalone shell scripts (
users/ami/scripts/): Linked into~/.local/bin/by Home Manager. - Inlined Nix scripts (
userVars.shellScripts): Declared in each user'svariables.nixand added to the user profile withpkgs.writeShellScriptBin(such askillclick,killcurrent,qrscan,qrcreate, and monitor focus helpers).
The majority of these are bound to Niri keybinds, or are user systemd services:
CLI task scripts
schedule.sh: A CLI task scheduler (inspired by axlefublr). You can queue commands from anywhere (schedule <command>), edit queued jobs in$EDITOR, check status (schedule ls), or look at stdout logs (schedule logs <id>).task-receiver.sh: A background daemon that listens for pending tasks and runs them insidesystemd-run --user --scopeunits. It listens to the Niri event stream to track spawned windows (like Ghostty), and if a task exits with an error, it opens afuzzeldialog to retry, drop, or edit the command viavipe.
Niri related scripts
niri-streamer.sh: A background listener forniri msg --json event-stream. It dispatches state changes to hooks underusers/ami/scripts/hooks/.hooks/urgency.sh: A listener for urgent windows or workspaces that sends notifications throughnotify-send, clearing them once focused.1-niri.sh,2-niri.sh,3-niri.sh,4-niri.sh: One-key preset window layout scripts. Definitely overengineered :p. They make sure target apps are running, stash unused distraction windows into theniriusscratchpad, and move columns across both monitors with proper widths.floating-term.sh: Spawns a centered floating terminal (Ghostty by default) with preset dimensions.smart-overview.sh: An overview toggle script that automatically focuses vertically centered workspaces that are in use per monitor.window-switcher.sh: A quick interactive window search and jump menu usingfuzzel.cast-picker.sh: A selector to choose specific windows or entire monitors for the niri Dynamic Cast Target.colour-picker.sh/focused-window-info.sh/focused-output-info.sh: Quick helpers that grab hex codes or window/output properties and send desktop notifications.
General scripts
todo.sh: A TODO and reminder manager usingfuzzel. It supports relative and absolute timestamps, on-boot reminders, and overdue tracking. It uses a systemd timer (--check) for periodic desktop notifications.anki-pomodoro.sh: A 25/5 Pomodoro timer usingtermdown. It automatically toggles DND mode usingwayleduring focus rounds and un-mutes during breaks.record.sh: A customgpu-screen-recorderwrapper with region (slurp), portal, or monitor capture, and audio options. It includes a post-recording menu to compress with FFmpeg (H.265), copy to clipboard, delete, and / or open the folder.smart-rebuild.sh: Spawns a floating terminal, pulls git updates, updates submodules, switches the NixOS flake with some custom limits, logs execution time, and can reboot or powers off after.theme-switcher.sh: For when I want to manually switch between light and dark system specialisations usingsudo switch-to-configuration testso it doesn't clutter the bootloader generations.zen-keyboard-shortcuts.sh: A custom tool that compares the shortcuts in the Nix config against Zen Browser's actual JSON file to make updating easier when there's a change to Zen's schema version.autoclicker.sh: A script that usesydotoolwith preset speed modes (50ms, anti-AFK, 100-click bursts, or custom intervals).view-clipboard-image.sh: Quickly opens the copied image or file URI inswayimg.rdp.sh: Anotherfuzzellauncher for FreeRDP and TigerVNC connecting to remote machines using credentials from Agenix.
More are available (that I've added myself to the code but not the below concise list), but these are the main ones. And it is, of course, trivial to add your own.
Important
Values are the file name without .nix.
Desktop session
Key in userVars.programs |
Available values | Upstream |
|---|---|---|
compositor |
niri |
niri |
desktop-shell |
wayle |
Wayle |
display-server |
wayland |
Wayland |
idler |
swayidle |
swayidle |
keyboard |
kanata |
Kanata |
killer |
earlyoom |
earlyoom |
launcher |
vicinae |
Vicinae |
login-manager |
greetd |
greetd |
nightlight |
sunsetr |
sunsetr |
Apps and tools
Key in userVars.programs |
Available values | Upstream |
|---|---|---|
browsers (list) |
zen-beta |
Zen Browser, BrowserOS |
editor |
hx |
Helix |
ergonomics |
safeeyes |
Safe Eyes |
explorer-gui |
dolphin, nemo |
Dolphin, Nemo |
explorer-tui |
yazi |
Yazi |
network-mounts |
nfs |
NFS remote mount automation |
pager |
ov |
ov |
partition-manager |
kde |
KDE Partition Manager |
remote-desktop |
freerdp, remmina |
FreeRDP, Remmina |
screen-recorder |
gpu-screen-recorder |
GPU Screen Recorder |
system-monitor |
missioncenter |
Mission Center |
vpn |
tailscale |
Tailscale |
Shell and prompt
Key in userVars.programs |
Available values | Upstream |
|---|---|---|
terminal |
ghostty |
Ghostty |
terminal-shell |
zsh |
Zsh |
prompt |
starship |
Starship |
visual |
zeditor |
Zed |
Extra modules (under programs.other)
These modules are under users/programs/misc/ and can be enabled by adding their name to the programs.other list in variables.nix:
affinity- Serif Affinity suite.anki- Spaced repetition flashcard software with custom add-ons & FSRS.atuin- Shell history sync and daemon search.aw- ActivityWatch automated time tracking & watchers.kde-connect- Device synchronization & clipboard integration.nix-direnv- Fast per-directory Nix devenvs.nix-index-database- Fast binary search & comma (nix-index) integration.nix-your-shell- Consistent subshell environments.opentabletdriver- Open source graphics tablet driver & daemon.steam- Steam with Proton-GE & GameMode support.styles- System-wide base16 theming, GTK/Qt synchronization, & daemon for the auto theme switching!typst- Fast markup-based typesetting system.ydotool- Wayland-compatible CLI for automated inputs.
TODO: Add screenshots or desktop previews here later :).
Just wanted to dedicate a section to thank the other amazing FOSS configs, and people, whose work inspired or directly helped parts of this setup 🩷:
- axlefublr: For inspiring the task scheduler setup, along with a bunch of shell concepts and ideas for my workflow.
- MangoCubes: For inspiring the inlined Nix shell scripts and several neat Niri binding helpers (like
killclick,killcurrent, andqrscan). - YaLTeR: For creating Niri, the best compositor I've ever used.
- BANanaD3V & sodiboo: For the fantastic Niri flakes.
- tsdh: For
nirius, which provides a wide bunch of extremely useful utilities for Niri - definitely saved me a lot of manual scripting! - 0xc000022070: For making an incredible
zen-browser-flake, and happily responding to my various comments and issues. - rodrada: For making an Anki addon which makes it responsively follow system theme, and even packaging it as a Nix flake :o!
- And many more which I have most definitely forgotten to put here 😅.
