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 show only evaluates the flake; it does not build anything.
  • nixos-rebuild build/test will actually evaluate/build the system closure and will catch missing packages/options earlier than switch.

Troubleshooting

  • Use sudo nixos-rebuild test --flake .#host before switching.
  • If Home Manager reports file collisions, move the existing file and rebuild.
  • If secrets fail, confirm /var/lib/sops-nix/key.txt exists on NixOS or set SOPS_AGE_KEY_FILE manually 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 = true
  • viAlias, vimAlias, and defaultEditor are 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-config here 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: ThinkPad
  • owais-nix-hp: HP

Add a machine

  1. Create conf/machines/{machine}/.

  2. Generate hardware config:

    sudo nixos-generate-config --show-hardware-config \
      > conf/machines/{machine}/hardware-configuration.nix
    
  3. Add configuration.nix importing hardware and (import ../../shared.nix).nixos.

  4. Add a nixosConfigurations.{hostname} entry in flake.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.nix
  • conf/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-daemon disabled
  • default TLP mode set to battery
  • CPU governor set to performance on AC and powersave on battery
  • fingerprint daemon enabled

owais-nix-hp

HP configuration.

Files:

  • conf/machines/hp/configuration.nix
  • conf/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

  1. Create conf/machines/{machine}/.

  2. Generate hardware config:

    sudo nixos-generate-config --show-hardware-config \
      > conf/machines/{machine}/hardware-configuration.nix
    
  3. Add configuration.nix that imports hardware config and (import ../../shared.nix).nixos.

  4. Set networking.hostName.

  5. Add a nixosConfigurations.{hostname} entry in flake.nix.

  6. 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 git@github.com

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 + 1 or { 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.nix package 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.sh can extract SOPS-managed SSH keys for non-NixOS use.

These parts are NixOS-specific and need native distro equivalents:

  • services.*, users.users.*, fonts.packages, and environment.systemPackages.
  • SOPS secrets mounted at /run/secrets/ by sops-nix.
  • nixos-rebuild aliases.
  • programs.nix-ld, unless you also install Nix and want similar dynamic linker behavior.

Use native packages for the desktop and services. Use per-language installers or Nix for fast-moving developer tools.

  1. Install a GNOME edition of your distro.
  2. Install the base workstation packages.
  3. Enable Docker, PostgreSQL, Redis, SSH, printing, and laptop power services.
  4. Install fonts.
  5. Copy dotfiles and app configs.
  6. 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 git@github.com

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.ghostty
  • conf/shared.nix: GNOME keybindings under dconf.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: installs zellij
  • conf/shared.nix: copies conf/modules/zellij
  • conf/modules/zellij/config.kdl: shared config
  • conf/modules/zellij/layouts/*.kdl: layouts
  • conf/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:

LayoutNotes
defaultsingle pane plus compact bar
classiclarge left pane, two stacked right panes, compact bar
idestrider file explorer, Neovim pane, execution and VCS panes
ide-stackNeovim-style top pane with a lower terminal pane
ide-stack-2Neovim-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 focus
  • Alt-[, Alt-]: cycle swap layouts
  • Alt-n: new pane
  • Alt-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-editor
  • conf/shared.nix: editor-tool-pkgs

Configured extensions:

  • basher
  • dart
  • elixir
  • flutter-snippets
  • gleam
  • lua
  • nix
  • oxocarbon

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: installs zathura and zathura_pdf_poppler
  • conf/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

KeyAction
uhalf page up
dhalf page down
Jfull page down
Kfull page up
Ttoggle page mode
rreload
Rrotate
Azoom in
Dzoom out
irecolor
pprint
btoggle 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-config input
  • conf/shared.nix: programs.neovim
  • conf/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 <desertthunder.dev@gmail.com>

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 'desertthunder.dev@gmail.com'

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.enable for NixOS
  • conf/shared.nix: users.users.owais.shell
  • conf/shared.nix: Home Manager programs.zsh

Oh My Zsh plugins:

  • git
  • z

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.starship
  • conf/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:

DirectorySymbol
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: installs ripgrep
  • conf/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.ssh
  • conf/shared.nix: sops.secrets
  • conf/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 git@github.com
ssh -T git@codeberg.org
ssh -T git@tangled.sh

If SSH ignores a key, run:

ssh -vT git@github.com

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.

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

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-config keeps personal rg config from changing project results.
  • --max-columns=1000 avoids dropping long lines too early.
  • Add -i for case-insensitive tags.
  • Add --hidden when 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.