Welcome
This site documents my NixOS workstation, dotfiles, editor setup, terminal workflow, and the parts that can be reused outside NixOS.
The repo is written for two audiences:
- me, when I need to rebuild a machine or remember why something is configured a certain way
- anyone trying to copy pieces of the setup on NixOS, Fedora, Ubuntu, Debian, or another Linux distro
What is here
- Guides: common commands, checks, secrets, and migration notes
- NixOS: flake layout, rebuilds, hosts, and SOPS wiring
- Programs: portable config notes for Ghostty, Zellij, Zed, Zathura, Neovim, Git, Zsh, Starship, ripgrep, and SSH
- Other Distros: how to recreate the working setup without NixOS
- Tools: small command-line notes and scripts
How to use it
If you are on NixOS, start with NixOS, then read Hosts and Secrets.
If you are not on NixOS, start with Other Distros. Then use the program pages for the tools you want to copy.
If you are editing this repo, read Development and Writing docs.
Repo shape
conf/
├── machines/ # host-specific NixOS config
├── modules/ # app config copied by Home Manager
├── secrets/ # SOPS-encrypted secrets
└── shared.nix # shared NixOS and Home Manager modules
docs/src/ # mdBook source
shells/ # project shell helpers
The generated book lives in docs/book/. Edit docs/src/ instead.
Guides
Common commands
# Build, test, and switch
sudo nixos-rebuild build --flake .#$(hostname)
sudo nixos-rebuild test --flake .#$(hostname)
sudo nixos-rebuild switch --flake .#$(hostname)
# Inspect generations
nixos-rebuild list-generations
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
# Garbage collect
sudo nix-collect-garbage -d
nix store optimise
# Update inputs
nix flake update
nix flake lock --update-input nixpkgs
# Hardware config
sudo nixos-generate-config --show-hardware-config \
> conf/machines/$(hostname)/hardware-configuration.nix
conf/shared.nix defines zsh aliases for common rebuild commands.
Home Manager
Home Manager is wired through the NixOS flake, so normal nixos-rebuild applies
both system and home changes. The shared Home Manager module is
(import ./conf/shared.nix).home in flake.nix.
Secrets
SOPS_AGE_KEY_FILE=$(pwd)/age.txt sops conf/secrets/owais.yaml
sops -d conf/secrets/owais.yaml
./conf/scripts/keys.sh
./conf/scripts/keys.sh extracts Git provider SSH keys into
~/.local/share/sops/ for non-NixOS use.
Check changes
When making small Nix changes (like adding packages), a quick local check flow:
# 1) Format (keeps diffs small and avoids nixfmt-related CI/lint churn)
nixfmt conf/shared.nix
# 2) Ensure the flake evaluates and outputs are well-formed
nix flake show --no-write-lock-file
# 3) Optional, stronger checks before switching
sudo nixos-rebuild build --flake .#$(hostname)
sudo nixos-rebuild test --flake .#$(hostname)
Notes:
nix flake showonly evaluates the flake; it does not build anything.nixos-rebuild build/testwill actually evaluate/build the system closure and will catch missing packages/options earlier thanswitch.
Troubleshooting
- Use
sudo nixos-rebuild test --flake .#hostbefore switching. - If Home Manager reports file collisions, move the existing file and rebuild.
- If secrets fail, confirm
/var/lib/sops-nix/key.txtexists on NixOS or setSOPS_AGE_KEY_FILEmanually for local operations. - If a flake path changed, search with
rg 'old/path'and update docs, scripts, and Nix imports together.
Migration checklist
- Copy hardware config into
conf/machines/{machine}/. - Keep host-only options in that machine's
configuration.nix. - Keep reusable system and home settings in
conf/shared.nix. - Rebuild with
sudo nixos-rebuild switch --flake .#{hostname}.
Writing docs
I'm a bit pedantic when it comes to my writing.
The key (for me) is to write these docs like working notes and not product copy.
Use direct sentences.
Name the command, file, or setting. Cut filler such as "it's worth noting", "the goal is", "this serves as", and "despite these challenges".
Prefer:
Run `nix flake show --no-write-lock-file` after editing `flake.nix`.
Avoid:
It's worth noting that this command serves as a useful way to validate the flake.
Checklist
Before committing docs:
- Remove filler openings.
- Replace passive voice when the actor matters.
- Keep one point per paragraph.
- Avoid em dashes.
- Avoid vague claims such as "important", "powerful", or "useful" unless the sentence explains why.
- Do not summarize a section after already explaining it.
- Use commands and paths when they answer the question faster than prose.
Development
This repository is a NixOS flake. Most changes are edits to conf/shared.nix, a
machine file under conf/machines/, or documentation under docs/src/.
Common checks:
nixfmt conf/shared.nix
nix flake show --no-write-lock-file
sudo nixos-rebuild build --flake .#$(hostname)
Use test before switching when a change could affect services, desktop
startup, login shells, or Home Manager activation:
sudo nixos-rebuild test --flake .#$(hostname)
The zsh aliases in conf/shared.nix wrap the common rebuild commands:
rebuild # switch
switch # switch
nboot # boot
tbuild # test
Docs are an mdBook in docs/. Edit source files in docs/src/; docs/book/ is
generated output.
cd docs
mdbook serve
Shells
Some application projects need native Linux libraries that should not live in
this machine's global profile. Tauri is the main example: Rust crates such as
glib-sys, gtk-sys, gdk-sys, and webkit2gtk-sys discover system libraries
through pkg-config. On NixOS those .pc files are not globally visible unless
a shell or package build exposes them.
For those cases, keep the dependency set in the application project as a
shell.nix. This keeps the global system configuration small and makes the app
build environment explicit.
Tauri
shell/tauri.nix contains a dev shell for the Tauri apps.
The shell provides:
- Rust tooling:
cargo,rustc,clippy,rustfmt,rust-analyzer - frontend tooling:
nodejs_24,pnpm - build discovery:
pkg-config - Tauri Linux libraries: GTK, GLib, WebKitGTK, libsoup, appindicator, and friends
- SQLite headers and library for
libsqlite3-sys
If a Tauri build reports a missing package such as glib-2.0, gdk-3.0, or
sqlite3, add the Nix package to the app's shell.nix. Do not add these Tauri
libraries to conf/shared.nix unless they are actually useful system-wide.
Neovim
Neovim is managed by Home Manager in conf/shared.nix, while the actual editor
configuration comes from the external neovim-config flake input.
home.file.".config/nvim" = {
source = neovim-config;
recursive = true;
};
Defaults
programs.neovim.enable = trueviAlias,vimAlias, anddefaultEditorare enabled.- Python and Ruby providers are kept enabled for compatibility.
Workflow
- Edit the upstream Neovim config repo for plugin/keymap/theme changes.
- Run
nix flake update --update-input neovim-confighere to pull changes. - Rebuild this system to install the updated config.
Quick keys
Common habits preserved from the existing config:
<leader>opens the main custom mappings namespace.- Telescope-style pickers are used for files, text, buffers, and help.
- LSP mappings cover definition, references, rename, code actions, and hover.
- Formatting and diagnostics are available through normal LSP commands.
Use Neovim's built-in helpers when unsure:
:map
:Telescope keymaps
:checkhealth
NixOS
Layout
Configuration lives under conf/:
conf/
├── machines/
│ ├── hp/
│ └── thinkpad/
├── modules/
├── secrets/
└── shared.nix
conf/shared.nix: shared NixOS and Home Manager modules.conf/machines/{machine}/configuration.nix: host-specific settings.conf/machines/{machine}/hardware-configuration.nix: generated hardware.conf/modules/: extra config assets used by Home Manager.conf/secrets/owais.yaml: encrypted SOPS secrets.
Rebuild
sudo nixos-rebuild switch --flake .#$(hostname)
sudo nixos-rebuild test --flake .#$(hostname)
nix flake update
Configurations currently provided by the flake:
nix-haxorus: ThinkPadowais-nix-hp: HP
Add a machine
-
Create
conf/machines/{machine}/. -
Generate hardware config:
sudo nixos-generate-config --show-hardware-config \ > conf/machines/{machine}/hardware-configuration.nix -
Add
configuration.niximporting hardware and(import ../../shared.nix).nixos. -
Add a
nixosConfigurations.{hostname}entry inflake.nix.
SOPS
The system imports sops-nix from conf/shared.nix and exposes secrets under
/run/secrets/.
Useful commands:
SOPS_AGE_KEY_FILE=$(pwd)/age.txt sops conf/secrets/owais.yaml
SOPS_AGE_KEY_FILE=$(pwd)/age.txt sops -d conf/secrets/owais.yaml
sops updatekeys conf/secrets/owais.yaml
The personal age key is documented in age.txt. .sops.yaml controls which
files are encrypted and which recipients can decrypt them.
Hosts
The flake builds two NixOS hosts. Both import conf/shared.nix and then add
machine-specific options from conf/machines/{machine}/configuration.nix.
nix-haxorus
ThinkPad configuration.
Files:
conf/machines/thinkpad/configuration.nixconf/machines/thinkpad/hardware-configuration.nix
Flake target:
sudo nixos-rebuild test --flake .#nix-haxorus
sudo nixos-rebuild switch --flake .#nix-haxorus
Host-specific settings:
- hostname:
nix-haxorus - thermal daemon enabled
- TLP enabled
power-profiles-daemondisabled- default TLP mode set to battery
- CPU governor set to
performanceon AC andpowersaveon battery - fingerprint daemon enabled
owais-nix-hp
HP configuration.
Files:
conf/machines/hp/configuration.nixconf/machines/hp/hardware-configuration.nix
Flake target:
sudo nixos-rebuild test --flake .#owais-nix-hp
sudo nixos-rebuild switch --flake .#owais-nix-hp
Host-specific settings:
- hostname:
owais-nix-hp
The HP file currently only imports shared config and hardware config.
Add another host
-
Create
conf/machines/{machine}/. -
Generate hardware config:
sudo nixos-generate-config --show-hardware-config \ > conf/machines/{machine}/hardware-configuration.nix -
Add
configuration.nixthat imports hardware config and(import ../../shared.nix).nixos. -
Set
networking.hostName. -
Add a
nixosConfigurations.{hostname}entry inflake.nix. -
Test before switching:
sudo nixos-rebuild test --flake .#{hostname}
Secrets
This repo stores secrets in conf/secrets/owais.yaml and decrypts them with
SOPS.
NixOS
conf/shared.nix imports sops-nix and reads the age key from:
/var/lib/sops-nix/key.txt
It exposes these secrets:
/run/secrets/keys_gh/run/secrets/keys_codeberg/run/secrets/keys_tangled
The files belong to user owais, group users, with mode 0600.
SSH uses those paths in the Home Manager config.
Local SOPS commands
Use the local age key when editing or reading the encrypted file from the repo:
SOPS_AGE_KEY_FILE=$(pwd)/age.txt sops conf/secrets/owais.yaml
SOPS_AGE_KEY_FILE=$(pwd)/age.txt sops -d conf/secrets/owais.yaml
sops updatekeys conf/secrets/owais.yaml
Non-NixOS key extraction
conf/scripts/keys.sh extracts Git SSH keys to:
~/.local/share/sops/
It expects the age key at:
~/.config/sops/age/keys.txt
Run:
mkdir -p ~/.config/sops/age
cp age.txt ~/.config/sops/age/keys.txt
./conf/scripts/keys.sh
The script writes:
~/.local/share/sops/keys_gh~/.local/share/sops/keys_codeberg~/.local/share/sops/keys_tangled
It also sets file mode 0600.
SSH config outside NixOS
Use normal home-directory paths outside NixOS:
Host github.com
HostName github.com
User git
IdentityFile ~/.local/share/sops/keys_gh
IdentitiesOnly yes
Host codeberg.org
HostName codeberg.org
User git
IdentityFile ~/.local/share/sops/keys_codeberg
IdentitiesOnly yes
Host tangled.sh
HostName tangled.org
User git
IdentityFile ~/.local/share/sops/keys_tangled
IdentitiesOnly yes
Set permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config ~/.local/share/sops/keys_*
Check secrets
sops -d conf/secrets/owais.yaml >/dev/null
ls -l /run/secrets/keys_*
ssh -T [email protected]
On non-NixOS, check the extracted key path instead of /run/secrets/.
Common failures
If SOPS cannot decrypt, check that SOPS_AGE_KEY_FILE points at the right age
key.
If NixOS rebuild fails on secrets, check that /var/lib/sops-nix/key.txt exists
on that machine.
If SSH ignores a key, check file permissions and confirm that the IdentityFile
path matches the distro.
Nix Notes
Language basics
- Strings:
"hello", paths:./file.nix, lists:[ a b c ]. - Attribute sets:
{ key = value; nested.x = 1; }. - Functions:
x: x + 1or{ pkgs, ... }: { }. let ... in ...binds local names.with pkgs; [ ripgrep fd ]brings attrs into scope.
Common patterns
Imports compose modules:
{
imports = [ ./hardware-configuration.nix ];
}
Package lists are plain Nix lists:
home.packages = with pkgs; [ fd zathura ];
Read JSON into Nix:
builtins.fromJSON (builtins.readFile ./conf/modules/omp.json)
Flakes
This repository's flake pins inputs for Nixpkgs, Home Manager, SOPS-Nix, and the
external Neovim config. nixosConfigurations maps host names to systems.
Modules
NixOS and Home Manager modules return option assignments. Shared modules are now
collected in conf/shared.nix as two attrs:
(import ./conf/shared.nix).nixos(import ./conf/shared.nix).home
Machine configs import the shared NixOS module and add host-specific options.
Other Linux distributions
This config builds a GNOME-based developer workstation on NixOS. You can get close on Fedora, Ubuntu, Debian, or derivatives by treating the Nix files as an inventory rather than as something your distro can apply directly.
The target system is:
- GNOME on Wayland with GDM, NetworkManager, PipeWire, printing, and OpenSSH.
- Zsh as the login shell, with Starship, Oh My Zsh, completion, autosuggestions, and syntax highlighting.
- Docker, PostgreSQL, and Redis available as local development services.
- Neovim, Zed, Ghostty, Zellij, Zathura, Firefox, Chrome, Spotify, mpv, and rofi.
- A broad CLI/dev-tool set for JS, Rust, Go, Elixir, Gleam, Flutter, Python, .NET, Android, Nix, Markdown, Typst, SQLite, and containers.
- Nerd fonts and a few UI fonts installed system-wide or per user.
- SSH keys decrypted from SOPS for GitHub, Codeberg, and Tangled.
The portable parts
These parts translate cleanly to other distributions:
conf/shared.nixpackage lists become distro packages, language installers, or Nix profile/Home Manager packages.conf/modules/zellij/can be copied to~/.config/zellij/.- The Neovim config comes from
github:desertthunder/nvim; clone it into~/.config/nvim. - Zathura, Ghostty, Git, Ripgrep, Starship, and Zsh settings can be written as normal dotfiles.
conf/scripts/keys.shcan extract SOPS-managed SSH keys for non-NixOS use.
These parts are NixOS-specific and need native distro equivalents:
services.*,users.users.*,fonts.packages, andenvironment.systemPackages.- SOPS secrets mounted at
/run/secrets/bysops-nix. nixos-rebuildaliases.programs.nix-ld, unless you also install Nix and want similar dynamic linker behavior.
Recommended approach
Use native packages for the desktop and services. Use per-language installers or Nix for fast-moving developer tools.
- Install a GNOME edition of your distro.
- Install the base workstation packages.
- Enable Docker, PostgreSQL, Redis, SSH, printing, and laptop power services.
- Install fonts.
- Copy dotfiles and app configs.
- Use Nix Home Manager later if you want declarative user packages on top of Fedora or Ubuntu.
Fedora setup
Start from Fedora Workstation. Then install the main packages:
sudo dnf upgrade --refresh
sudo dnf install \
git curl wget vim neovim zsh \
bat fd-find ripgrep fzf jq yq just tree file which \
fastfetch btop dust gnupg2 direnv starship \
zellij zathura zathura-pdf-poppler mpv ffmpeg yt-dlp \
gcc gcc-c++ make pkgconf-pkg-config clang-tools-extra \
nodejs pnpm golang gopls rust cargo rustfmt clippy \
python3 python3-pip python3-ipython java-17-openjdk-devel \
shellcheck ShellCheck shfmt sqlite \
docker docker-compose postgresql-server postgresql-contrib redis \
openssh-server cups lm_sensors lsof strace ltrace sysstat \
pciutils usbutils ethtool iotop iftop \
gnome-tweaks gnome-extensions-app rofi
Some package names vary by Fedora release. If one fails, search it:
dnf search zellij
dnf search zathura
dnf search language-server
Enable services:
sudo systemctl enable --now sshd
sudo systemctl enable --now cups
sudo systemctl enable --now docker
sudo systemctl enable --now redis
sudo usermod -aG docker "$USER"
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
Create a local PostgreSQL role and database:
sudo -iu postgres createuser --createdb --createrole "$USER"
sudo -iu postgres createdb -O "$USER" "$USER"
For ThinkPad-like laptop behavior:
sudo dnf install thermald tlp tlp-rdw fprintd
sudo systemctl enable --now thermald
sudo systemctl disable --now power-profiles-daemon
sudo systemctl enable --now tlp
sudo systemctl enable --now fprintd
Fedora may already manage power well with power-profiles-daemon. Pick either
that or TLP, not both.
Ubuntu/Debian setup
Start from Ubuntu Desktop, Debian GNOME, Pop!_OS, Linux Mint, or another Debian-based GNOME-ish system.
sudo apt update
sudo apt upgrade
sudo apt install \
git curl wget vim neovim zsh \
bat fd-find ripgrep fzf jq just tree file gnupg direnv \
btop zathura zathura-pdf-poppler mpv ffmpeg yt-dlp \
build-essential pkg-config clangd clang-tools shellcheck shfmt sqlite3 \
nodejs npm golang gopls rustc cargo rustfmt \
python3 python3-pip ipython3 openjdk-17-jdk \
docker.io docker-compose-v2 postgresql postgresql-contrib redis-server \
openssh-server cups lm-sensors lsof strace ltrace sysstat \
pciutils usbutils ethtool iotop iftop \
gnome-tweaks gnome-shell-extension-manager rofi
On Debian and Ubuntu, the bat package may install the command as batcat.
Create a user-local shim if needed:
mkdir -p ~/.local/bin
ln -sf /usr/bin/batcat ~/.local/bin/bat
On Debian stable or Ubuntu LTS, several development tools will be old. Use the upstream installer for tools where version matters:
# Starship
curl -sS https://starship.rs/install.sh | sh
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Bun
curl -fsSL https://bun.sh/install | bash
# uv
curl -LsSf https://astral.sh/uv/install.sh | sh
Enable services:
sudo systemctl enable --now ssh
sudo systemctl enable --now cups
sudo systemctl enable --now docker
sudo systemctl enable --now redis-server
sudo usermod -aG docker "$USER"
Create a local PostgreSQL role and database:
sudo -iu postgres createuser --createdb --createrole "$USER"
sudo -iu postgres createdb -O "$USER" "$USER"
For laptop power and fingerprint support:
sudo apt install thermald tlp fprintd libpam-fprintd
sudo systemctl enable --now thermald
sudo systemctl disable --now power-profiles-daemon 2>/dev/null || true
sudo systemctl enable --now tlp
Shell and prompt
Switch to Zsh:
chsh -s "$(command -v zsh)"
Install Oh My Zsh if your distro package is missing or too old:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Put the important PATH additions from conf/shared.nix in ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="$HOME/go/bin:$PATH"
eval "$(starship init zsh)"
Useful aliases from the NixOS config that still make sense elsewhere:
alias ll='ls -l'
alias cat='bat --paging=never --style=plain'
alias less='bat'
alias preview='bat --style=numbers,changes --color=always'
alias zed='zeditor'
alias zedn='zeditor --new'
Do not copy the rebuild, switch, update, nboot, and tbuild aliases.
Those call nixos-rebuild.
App configs
Copy or recreate these files:
mkdir -p ~/.config
cp -r conf/modules/zellij ~/.config/zellij
git clone https://github.com/desertthunder/nvim ~/.config/nvim
Ripgrep config:
mkdir -p ~/.config/ripgrep
cat > ~/.config/ripgrep/config <<'EOF'
--line-number
--smart-case
--max-columns=120
--max-columns-preview
--type-add=nix:*.nix
--glob=!.git/*
--glob=!**/node_modules/**
--glob=!**/target/**
--glob=!**/.build/**
EOF
Zellij config can be checked with:
ZELLIJ_CONFIG_FILE="$HOME/.config/zellij/config.kdl" \
ZELLIJ_CONFIG_DIR="$HOME/.config/zellij" \
zellij setup --check
GNOME settings
The Nix config sets the Kora icon theme and two keyboard shortcuts for Ghostty. On other distros, install Ghostty from its upstream packages and then run:
gsettings set org.gnome.desktop.interface icon-theme 'kora'
gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings \
"['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/', '/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/']"
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ name 'Ghostty'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ command 'ghostty'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ binding '<Control><Alt>t'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ name 'Ghostty (zellij)'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ command 'ghostty -e zellij'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ binding '<Super>z'
Fonts
Install as many of these as your distro packages provide:
- JetBrains Mono Nerd Font
- Commit Mono Nerd Font
- Hack Nerd Font
- Monaspace Nerd Font
- 0xProto Nerd Font
- iA Writer/IM Writing Nerd Font
- Maple Mono NF
- Comic Mono
- Open Sans
- Inter
- Noto Fonts
- Ubuntu Classic
- Fira Mono
On Fedora, many base fonts are available through DNF. Nerd Fonts are often easier with a user install:
mkdir -p ~/.local/share/fonts
# Download font zip files from https://www.nerdfonts.com/font-downloads,
# unzip them into ~/.local/share/fonts, then refresh the cache.
fc-cache -fv
Secrets and SSH
NixOS uses sops-nix to place SSH keys in /run/secrets/. Other distros need a
normal file path under your home directory.
If you have the age key for this repo, put it where conf/scripts/keys.sh
expects it and extract the keys:
mkdir -p ~/.config/sops/age
cp age.txt ~/.config/sops/age/keys.txt
./conf/scripts/keys.sh
Then point SSH at the extracted files. A non-NixOS ~/.ssh/config should look
like this:
Host github.com
HostName github.com
User git
IdentityFile ~/.local/share/sops/keys_gh
IdentitiesOnly yes
Host codeberg.org
HostName codeberg.org
User git
IdentityFile ~/.local/share/sops/keys_codeberg
IdentitiesOnly yes
Host tangled.sh
HostName tangled.org
User git
IdentityFile ~/.local/share/sops/keys_tangled
IdentitiesOnly yes
Set strict permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config ~/.local/share/sops/keys_*
Optional: install Nix on Fedora or Ubuntu
If you want the package set to behave more like this repo, install Nix and use it for user packages:
sh <(curl -L https://nixos.org/nix/install) --daemon
mkdir -p ~/.config/nix
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
This repository does not currently expose a standalone
homeConfigurations.<user> output. To use Home Manager outside NixOS, add one
or create a separate home flake that imports (import ./conf/shared.nix).home.
You will still need to replace the NixOS-only secret paths and any Linux desktop
assumptions that do not match your distro.
Sanity check
After setup, log out and back in so group membership and shell changes apply. Then check:
zsh --version
starship --version
nvim --version
zellij --version
docker run hello-world
psql -d "$USER" -c 'select version();'
redis-cli ping
ssh -T [email protected]
Expect package substitutions. Aim for the same working setup: GNOME, Zsh, modern terminals and editors, local services, container tooling, language servers, and the same dotfiles.
Dotfiles
SOPS without NixOS
Use the local age key with the encrypted file under conf/secrets/:
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
sops -d conf/secrets/owais.yaml
Extract Git SSH keys manually:
mkdir -p ~/.local/share/sops
for key in keys_gh keys_codeberg keys_tangled; do
sops --extract "[\"$key\"]" -d conf/secrets/owais.yaml \
> ~/.local/share/sops/$key
done
chmod 600 ~/.local/share/sops/keys_*
Or run:
./conf/scripts/keys.sh
For NixOS, the same secrets are exposed through /run/secrets/ by sops-nix.
Programs
These pages document programs with reusable config. Each page lists the Nix source and the portable setup for non-Nix systems.
Focus on copied config, shell setup, keybindings, and commands that verify the program works. The package inventory stays in the Nix files.
Ghostty
What this config does
Ghostty uses Zsh as a login shell, JetBrains Mono Nerd Font, zsh shell integration, and a small dark palette. GNOME shortcuts launch Ghostty and Ghostty with Zellij.
Nix location
conf/shared.nix:programs.ghosttyconf/shared.nix: GNOME keybindings underdconf.settings
Portable setup
Install Ghostty from your distro or from upstream packages. Install JetBrains Mono Nerd Font before copying the font setting.
Create or edit ~/.config/ghostty/config:
font-family = JetBrainsMono Nerd Font Propo
font-style = SemiBold
font-size = 16
window-padding-x = 8
window-padding-y = 8
background = 1b1b1b
foreground = ffffff
cursor-color = 78a9ff
cursor-text = 161616
mouse-hide-while-typing = true
copy-on-select = false
confirm-close-surface = false
shell-integration = zsh
command = zsh --login
Add the palette if you want the same colors:
palette = 0=#161616
palette = 1=#ee5396
palette = 2=#42be65
palette = 3=#ff7eb6
palette = 4=#33b1ff
palette = 5=#be95ff
palette = 6=#3ddbd9
palette = 7=#ffffff
palette = 8=#525252
palette = 9=#ee5396
palette = 10=#42be65
palette = 11=#ff7eb6
palette = 12=#33b1ff
palette = 13=#be95ff
palette = 14=#3ddbd9
palette = 15=#ffffff
On NixOS the command path is /run/current-system/sw/bin/zsh --login. Use
zsh --login outside NixOS.
GNOME shortcuts
gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings \
"['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/', '/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/']"
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ name 'Ghostty'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ command 'ghostty'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty/ binding '<Control><Alt>t'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ name 'Ghostty (zellij)'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ command 'ghostty -e zellij'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/ghostty-zellij/ binding '<Super>z'
Check it
ghostty --version
fc-match 'JetBrainsMono Nerd Font Propo'
zsh --version
Zellij
What this config does
Zellij uses the repo config from conf/modules/zellij. Home Manager copies that
directory to ~/.config/zellij.
Nix location
conf/shared.nix: installszellijconf/shared.nix: copiesconf/modules/zellijconf/modules/zellij/config.kdl: shared configconf/modules/zellij/layouts/*.kdl: layoutsconf/modules/zellij/themes/*.kdl: themes
Portable setup
Install Zellij, then copy the config:
mkdir -p ~/.config
cp -r conf/modules/zellij ~/.config/zellij
Validate it:
ZELLIJ_CONFIG_FILE="$HOME/.config/zellij/config.kdl" \
ZELLIJ_CONFIG_DIR="$HOME/.config/zellij" \
zellij setup --check
Layouts
Available local layouts:
| Layout | Notes |
|---|---|
default | single pane plus compact bar |
classic | large left pane, two stacked right panes, compact bar |
ide | strider file explorer, Neovim pane, execution and VCS panes |
ide-stack | Neovim-style top pane with a lower terminal pane |
ide-stack-2 | Neovim-style pane, side pane, and testing pane |
Run a layout by name:
zellij --layout ide
zellij -l ide-stack
Run a layout by path while editing it:
zellij --layout ~/.config/zellij/layouts/classic.kdl
Keys
Shared config starts in locked mode. Press Ctrl-g to toggle locked and normal
mode.
Common bindings:
Alt-h,Alt-j,Alt-k,Alt-l: move focusAlt-[,Alt-]: cycle swap layoutsAlt-n: new paneAlt-f: toggle floating panes
Check it
zellij --version
zellij setup --check
Zed
What this config does
Zed uses Vim mode, the Oxocarbon theme, Inter for the UI, 0xProto Nerd Font for buffers, and format-on-save. Home Manager installs language tools so Zed can find them when launched from the desktop.
Nix location
conf/shared.nix:programs.zed-editorconf/shared.nix:editor-tool-pkgs
Configured extensions:
basherdartelixirflutter-snippetsgleamluanixoxocarbon
Portable setup
Install Zed and install the extensions from Zed's extension UI.
Install the language servers and formatters you need. This repo configures:
bash-language-server
clang-tools
dprint
elixir
elixir-ls
eslint_d
flutter
gleam
go
gopls
gotools
lua-language-server
nil
nixd
nixfmt
nodejs
rust-analyzer
rustfmt
shellcheck
shfmt
stylua
typescript
typescript-language-server
On non-Nix systems, GUI apps may not inherit your shell PATH. If Zed cannot find a language server, launch it from a terminal:
zeditor .
Or set PATH for desktop sessions through your distro's environment mechanism.
Settings
The Nix config maps to these Zed settings:
{
"theme": "Oxocarbon Dark (Variant I)",
"vim_mode": true,
"ui_font_family": "Inter",
"ui_font_size": 18.0,
"buffer_font_family": "0xProto Nerd Font Propo",
"buffer_font_size": 18,
"tab_size": 2,
"hard_tabs": false,
"format_on_save": "on",
"telemetry": {
"diagnostics": false,
"metrics": false
}
}
Check it
zeditor --version
which nixd
which typescript-language-server
which rust-analyzer
Open Zed's language server logs if completion or formatting fails.
Zathura
What this config does
Zathura uses a dark Catppuccin-like palette, clipboard selection, zero page padding, custom navigation keys, and the Poppler PDF backend.
Nix location
conf/shared.nix: installszathuraandzathura_pdf_popplerconf/shared.nix:programs.zathura
Portable setup
Install Zathura and the PDF plugin.
Fedora:
sudo dnf install zathura zathura-pdf-poppler
Ubuntu/Debian:
sudo apt install zathura zathura-pdf-poppler
Create ~/.config/zathura/zathurarc and copy the settings you want from
conf/shared.nix.
Mappings
| Key | Action |
|---|---|
u | half page up |
d | half page down |
J | full page down |
K | full page up |
T | toggle page mode |
r | reload |
R | rotate |
A | zoom in |
D | zoom out |
i | recolor |
p | |
b | toggle status bar |
Useful portable config
set selection-clipboard clipboard
set recolor false
set page-padding 0
map u scroll half-up
map d scroll half-down
map J scroll full-down
map K scroll full-up
map T toggle_page_mode
map r reload
map R rotate
map A zoom in
map D zoom out
map i recolor
map p print
map b toggle_statusbar
Check it
zathura --version
zathura sample.pdf
Neovim
What this config does
Home Manager enables Neovim, sets it as the default editor, and copies the
external Neovim config into ~/.config/nvim.
Nix location
flake.nix:neovim-configinputconf/shared.nix:programs.neovimconf/shared.nix:home.file.".config/nvim"
The flake input points at:
github:desertthunder/nvim
Portable setup
Clone the config directly:
git clone https://github.com/desertthunder/nvim ~/.config/nvim
Install Neovim and the language tools used by your projects. The broader editor
tool list lives in conf/shared.nix under editor-tool-pkgs.
Nix workflow
Update the external config input from this repo:
nix flake update --update-input neovim-config
sudo nixos-rebuild switch --flake .#$(hostname)
Portable workflow
Update the cloned config directly:
cd ~/.config/nvim
git pull
Check it
Inside Neovim:
:checkhealth
:map
:Telescope keymaps
From the shell:
nvim --version
Git
What this config does
Home Manager configures Git identity and a global ignore list.
Nix location
conf/shared.nix:programs.git
Configured identity:
Owais Jamil <[email protected]>
Global ignores:
.DS_Store
Thumbs.db
*~
*.swp
*.swo
.env
.env.*
!.env.example
.direnv/
.devenv/
result
result-*
.sandbox/
AGENTS.md
CLAUDE.md
Portable setup
Set identity:
git config --global user.name 'Owais Jamil'
git config --global user.email '[email protected]'
Create a global ignore file:
cat > ~/.gitignore_global <<'EOF'
.DS_Store
Thumbs.db
*~
*.swp
*.swo
.env
.env.*
!.env.example
.direnv/
.devenv/
result
result-*
.sandbox/
AGENTS.md
CLAUDE.md
EOF
git config --global core.excludesfile ~/.gitignore_global
SSH remotes
SSH host config lives on the SSH page. SOPS key extraction lives on the Secrets page.
Check it
git config --global --get user.name
git config --global --get user.email
git config --global --get core.excludesfile
Zsh
What this config does
NixOS sets Zsh as the login shell for user owais. Home Manager enables
completion, autosuggestions, syntax highlighting, Oh My Zsh, and Starship.
Nix location
conf/shared.nix:programs.zsh.enablefor NixOSconf/shared.nix:users.users.owais.shellconf/shared.nix: Home Managerprograms.zsh
Oh My Zsh plugins:
gitz
Portable setup
Install Zsh and make it your login shell:
chsh -s "$(command -v zsh)"
Install Oh My Zsh if your distro does not package it:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Add PATH entries to ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="$HOME/go/bin:$PATH"
Initialize Starship:
eval "$(starship init zsh)"
Aliases worth copying
alias ll='ls -l'
alias cat='bat --paging=never --style=plain'
alias less='bat'
alias preview='bat --style=numbers,changes --color=always'
alias zed='zeditor'
alias zedn='zeditor --new'
Do not copy these outside NixOS:
alias rebuild='sudo nixos-rebuild switch --flake ~/Projects/nixos-conf#$(hostname)'
alias switch='sudo nixos-rebuild switch --flake ~/Projects/nixos-conf#$(hostname)'
alias update='sudo nixos-rebuild switch --flake ~/Projects/nixos-conf#$(hostname)'
alias nboot='sudo nixos-rebuild boot --flake ~/Projects/nixos-conf#$(hostname)'
alias tbuild='sudo nixos-rebuild test --flake ~/Projects/nixos-conf#$(hostname)'
Check it
zsh --version
echo "$SHELL"
starship --version
Log out and back in after changing the login shell.
Starship
What this config does
Starship provides the shell prompt for Zsh. The prompt shows time, Nix icon, user, host, directory, Git branch and status, command duration, Python context, and the prompt character.
Nix location
conf/shared.nix:programs.starshipconf/shared.nix:programs.zsh.enableZshIntegration
Catppuccin integration is enabled globally, but Starship's Catppuccin module is disabled. The prompt uses explicit Starship settings instead.
Portable setup
Install Starship:
curl -sS https://starship.rs/install.sh | sh
Add this to ~/.zshrc:
eval "$(starship init zsh)"
Create ~/.config/starship.toml if you want to port the full prompt. Translate
the settings from conf/shared.nix under programs.starship.settings.
Prompt pieces
Configured modules:
- time
- username
- hostname
- directory
- Git branch
- Git state
- Git status
- command duration
- Python
- character
Directory substitutions:
| Directory | Symbol |
|---|---|
Documents | |
Downloads | |
Music | |
Pictures | |
Check it
starship --version
starship explain
Open a Git repo and run a slow command to check Git status and command duration.
ripgrep
What this config does
Home Manager installs ripgrep and writes a default config to
~/.config/ripgrep/config.
Nix location
conf/shared.nix: installsripgrepconf/shared.nix:home.file.".config/ripgrep/config"
Portable setup
Install ripgrep:
Fedora:
sudo dnf install ripgrep
Ubuntu/Debian:
sudo apt install ripgrep
Create the config file:
mkdir -p ~/.config/ripgrep
cat > ~/.config/ripgrep/config <<'EOF'
--line-number
--smart-case
--max-columns=120
--max-columns-preview
--type-add=nix:*.nix
--glob=!.git/*
--glob=!**/node_modules/**
--glob=!**/target/**
--glob=!**/.build/**
EOF
Common commands
rg pattern
rg -i pattern
rg "fn name" -t nix
rg pattern path/to/dir
rg -n -C 2 pattern
rg --files
rg --files -g '*.nix'
Check it
rg --version
rg --files -t nix
Use --no-config when scripts need results that do not depend on personal rg
settings.
SSH
What this config does
Home Manager writes SSH host entries for GitHub, Codeberg, and Tangled. On NixOS
the keys come from /run/secrets/ through sops-nix.
Nix location
conf/shared.nix:programs.sshconf/shared.nix:sops.secretsconf/secrets/owais.yaml: encrypted key material
NixOS paths
Host github.com
HostName github.com
User git
IdentityFile /run/secrets/keys_gh
IdentitiesOnly yes
Host codeberg.org
HostName codeberg.org
User git
IdentityFile /run/secrets/keys_codeberg
IdentitiesOnly yes
Host tangled.sh
HostName tangled.org
User git
IdentityFile /run/secrets/keys_tangled
IdentitiesOnly yes
The config sets AddKeysToAgent no for all hosts.
Portable setup
Extract the keys first. See Secrets.
Use home-directory paths outside NixOS:
Host github.com
HostName github.com
User git
IdentityFile ~/.local/share/sops/keys_gh
IdentitiesOnly yes
Host codeberg.org
HostName codeberg.org
User git
IdentityFile ~/.local/share/sops/keys_codeberg
IdentitiesOnly yes
Host tangled.sh
HostName tangled.org
User git
IdentityFile ~/.local/share/sops/keys_tangled
IdentitiesOnly yes
Set permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config ~/.local/share/sops/keys_*
Check it
ssh -T [email protected]
ssh -T [email protected]
ssh -T [email protected]
If SSH ignores a key, run:
ssh -vT [email protected]
Check the Offering public key lines and confirm the path matches your config.
Tools
ripgrep
Ripgrep is installed by Home Manager and configured at
~/.config/ripgrep/config from conf/shared.nix.
Common searches:
rg pattern
rg -i pattern
rg "fn name" -t nix
rg pattern path/to/dir
rg -n -C 2 pattern
rg --files
rg --files -g '*.nix'
Filtering:
rg pattern -g '*.nix'
rg pattern -g '!**/node_modules/**'
rg pattern --hidden -g '!.git'
rg pattern -t md
Output:
rg pattern --json
rg pattern --count
rg pattern --files-with-matches
rg pattern --replace replacement
Project defaults:
--line-number
--smart-case
--max-columns=120
--max-columns-preview
--type-add=nix:*.nix
--glob=!.git/*
--glob=!**/node_modules/**
--glob=!**/target/**
--glob=!**/.build/**
Other CLI tools
Home Manager installs common helpers including fd, jq, yq, fzf, bat,
dust, tree, zellij, gum, glow, vhs, btop, shellcheck, shfmt,
typst, and mdbook.
TODO Comment Search
Use ripgrep (rg) to find TODO-style comments quickly across projects. This
is useful for local cleanup, release checks, and CI gates.
Quick search
rg --no-messages --vimgrep -H --column --line-number --color never \
-e '(TODO|FIXME|BUG|HACK|XXX)' .
--vimgrep, -H, --column, and --line-number produce rows in this form:
path/to/file:line:column:matched text
Comment-aware search
This pattern looks for common comment prefixes, markdown task/list items, or a tag at the start of a line:
TAGS='BUG|HACK|FIXME|TODO|XXX|\[ \]|\[x\]'
PREFIX='//|#|<!--|;|/\*|^|^[[:blank:]]*(-|[0-9]+\.)'
rg --no-messages --vimgrep -H --column --line-number --color never \
--max-columns=1000 --no-config \
-e "(${PREFIX})[[:space:]]*(${TAGS})" \
-g '!**/.git/**' \
-g '!**/node_modules/**' \
-g '!**/target/**' \
-g '!**/.build/**' \
.
Notes:
--no-configkeeps personal rg config from changing project results.--max-columns=1000avoids dropping long lines too early.- Add
-ifor case-insensitive tags. - Add
--hiddenwhen dotfiles should be scanned too.
Project script
Add this as scripts/todos:
#!/usr/bin/env bash
set -euo pipefail
TAGS='BUG|HACK|FIXME|TODO|XXX|\[ \]|\[x\]'
PREFIX='//|#|<!--|;|/\*|^|^[[:blank:]]*(-|[0-9]+\.)'
rg --no-messages --vimgrep -H --column --line-number --color never \
--max-columns=1000 --no-config \
-e "(${PREFIX})[[:space:]]*(${TAGS})" \
-g '!**/.git/**' \
-g '!**/node_modules/**' \
-g '!**/target/**' \
-g '!**/.build/**' \
"$@" \
.
Then:
chmod +x scripts/todos
scripts/todos
Filters
Use -g for include and exclude globs. A glob beginning with ! excludes
matches.
# Only source and markdown paths
scripts/todos -g 'src/**' -g 'docs/**' -g '*.md'
# Exclude generated/vendor paths
scripts/todos -g '!**/vendor/**' -g '!**/dist/**' -g '!**/*.lock'
# Include hidden files
scripts/todos --hidden
CI check
Fail when TODO-style comments are present:
if scripts/todos >/tmp/todos.txt; then
cat /tmp/todos.txt
echo "TODO comments found"
exit 1
fi
rg exits with 0 when it finds a match and 1 when it finds none.