Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nixos-config

Reusable NixOS, nix-darwin, and home-manager modules by Ananth Bhaskararaman.

This flake exports modules you can import into your own Nix configurations: a complete dev environment (fish + a yazelix zellij/yazi workspace + nixvim with 30+ plugins), a minimal bash baseline, and NixOS infrastructure modules for Cloudflare tunnels, Tailscale, rclone sync, backup helpers, and more.

What’s included

  • NixOS modules for Cloudflare tunnels, Tailscale serve configs, rclone sync jobs, systemd service targets, backup helpers, and a curated set of self-hosted services (Immich, Jellyfin, Frigate, VictoriaMetrics, Grafana, Vaultwarden, Mealie, Actual, Seafile, …).
  • Home-manager modules: a minimal shell (bash + history + aliases) and a dev that bundles fish + starship + yazelix (zellij + yazi) + nixvim with 30+ plugins + the usual CLI sidekicks (atuin, bat, eza, fd, fzf, gh, glab, gpg, lazygit, ripgrep, zoxide, delta).
  • Parameterized options (machines.*) so you can override the username, timezone, locale, vault address, and other defaults.

Quick start

Add this flake as an input and import the modules you want:

{
  inputs.nixos-config.url = "github:ananthb/nixos-config";

  outputs = { nixos-config, nixpkgs, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      modules = [
        nixos-config.nixosModules.default
        {
          machines.username = "alice";
          machines.timeZone = "America/New_York";
        }
      ];
    };
  };
}

For home-manager only:

home-manager.sharedModules = [
  nixos-config.homeManagerModules.shell  # minimal bash
  # or nixos-config.homeManagerModules.dev  # fish + yazelix + nixvim + tools
  # or nixos-config.homeManagerModules.default  # shell + dev together
];

License

GPLv3. See LICENSE.

Getting Started

Add the flake input

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nixos-config = {
      url = "github:ananthb/nixos-config";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
}

Import NixOS modules

Import the default bundle (all modules) or pick individual ones:

# All NixOS modules
modules = [ nixos-config.nixosModules.default ];

# Or just what you need
modules = [
  nixos-config.nixosModules.options
  nixos-config.nixosModules.scripts
  nixos-config.nixosModules.rclone-sync
];

Then configure the options:

{
  machines.username = "alice";
  machines.timeZone = "America/New_York";
  machines.locale = "en_US.UTF-8";
}

Import home-manager modules

Pass inputs via extraSpecialArgs (needed for nixvim):

home-manager = {
  extraSpecialArgs = { inherit inputs; };
  users.alice = {
    imports = [
      nixos-config.homeManagerModules.default  # shell + dev
    ];
  };
};

Or import just the shell:

imports = [ nixos-config.homeManagerModules.shell ];

Dependencies

Not all modules are fully standalone. Some depend on vault-secrets (the HashiCorp Vault credential injection module) or on other modules from this flake.

ModuleStandalone?Dependencies
homeManagerModules.shellYesNone
homeManagerModules.devYesnixvim (provided transitively via this flake’s inputs)
nixosModules.nix-settingsYesNone
nixosModules.service-targetYesnixosModules.options (for machines.serviceTarget.name)
nixosModules.tailscale-serveYesNone
nixosModules.rclone-syncNoRequires nixosModules.scripts (for my-scripts.shell-helpers)
nixosModules.scriptsNoRequires vault-secrets NixOS module (declares kopia/gcloud secrets)
nixosModules.cftunnelNoRequires vault-secrets NixOS module (stores tunnel credentials)

If you want to use rclone-sync, scripts, or cftunnel, you’ll also need vault-secrets in your flake inputs and imported into your NixOS configuration.

The home-manager modules (shell, dev) are fully independent and can be used in any home-manager setup without NixOS or vault-secrets.

Service modules

The service modules in services/ (Seafile, Jellyfin, Immich, arr stack, monitoring, etc.) are exported under names like nixosModules.media-jellyfin, nixosModules.monitoring-grafana, nixosModules.seafile, and so on — see the NixOS Modules page for the full list. They were written for one specific fleet and bake in opinions (vault secret paths, domains, container images) that may not match yours; fork-and-customize is often the right path. The Fork and Customize guide walks through that.

NixOS Modules

options

Declares the machines.* option namespace. Imported automatically by default.

See Options Reference for the full list.

scripts

Backup helpers and shell utilities for systemd services.

Options:

  • my-services.mkBackupService — function to create kopia backup services with common boilerplate
  • my-scripts.victoriaMetricsHost — host running VictoriaMetrics (for write_metric helper)
  • my-scripts.shell-helpers — sourceable shell script with die, write_metric, and other utilities

Usage:

{
  my-scripts.victoriaMetricsHost = "monitoring-host";

  systemd.services.my-backup = config.my-services.mkBackupService {
    name = "my-backup";
    paths = [ "/var/lib/myapp" ];
    # ...
  };
}

cftunnel

Declarative Cloudflare Tunnel configuration with automatic vault-secrets wiring.

Options:

  • my-services.cftunnelConfig — list of tunnel configurations

Usage:

{
  my-services.cftunnelConfig = [
    {
      tunnelId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
      tunnelName = "my-tunnel";
      ingress = {
        "app.example.com" = "http://localhost:3000";
        "api.example.com" = "http://localhost:8080";
      };
    }
  ];
}

Credentials are automatically fetched from Vault via vault-secrets.secrets.cloudflare-tunnel-<id>.

tailscale-serve

Applies a Tailscale serve configuration at boot.

Options:

  • my-services.tailscaleServeConfig — Tailscale serve config (JSON-serializable attrset)

Requires networking.hostName to be set. Waits for tailscaled to reach “Running” state before applying.

service-target

Groups systemd services under a common target for batch restart.

Options:

  • my-services.kediTargets — attrset of service names to include (set to true)
  • my-services.restartUnits — extra units to include
  • my-services.restartUnitsExclude — units to exclude

The target name is configurable via machines.serviceTarget.name (default: "kedi").

Usage:

{
  machines.serviceTarget.name = "mystack";
  my-services.kediTargets = {
    jellyfin = true;
    sonarr = true;
    radarr = true;
  };
}
# Creates mystack.target that wants jellyfin.service, sonarr.service, radarr.service
# Restart all: systemctl restart mystack.target

rclone-sync

Declarative rclone sync and bisync jobs as systemd services with timers.

Options:

  • my-services.rclone-syncs.<name> — sync job configuration

Usage:

{
  my-services.rclone-syncs.photos-backup = {
    type = "sync";  # or "bisync"
    source = "/home/user/Photos";
    destination = "gdrive:Backups/Photos";
    rcloneConfig = "/run/secrets/rclone/config";
    interval = "daily";
    excludePatterns = [ "*.tmp" ];
  };
}

Default exclude patterns cover macOS, Windows, and Linux system files.

nix-settings

Shared Nix configuration: enables flakes, sets the Garnix binary cache, and other defaults.

Home Manager Modules

shell

A minimal bash baseline: large dedup’d history, sensible shell options (histappend, checkwinsize, extglob, globstar, checkjobs), and a handful of aliases (ll, la, gs, gd, gl). Import this if you want a slightly nicer login shell on hosts that don’t run the full dev environment.

dev

The full day-to-day environment: fish + starship + a yazelix-driven zellij/yazi workspace, nixvim with 30+ plugins (LSP for ~15 languages, treesitter + textobjects, telescope, harpoon, oil, flash, surround, copilot, DAP, conform), and CLI tools (atuin, bat, eza, fd, fzf, gh, glab, gpg, lazygit, ripgrep, zoxide, delta).

The dev module does not set git identity, ssh keys, or any sops wiring — those belong in your personal config. See Dev Environment for the keymap reference.

options

Declares machines.username for home-manager modules. Imported automatically by default.

default

Aggregator: options + shell + dev.

Options Reference

All options are under the machines namespace.

NixOS / nix-darwin options

Declared in modules/options.nix.

OptionTypeDefaultDescription
machines.usernamestr"ananth"Primary user account name
machines.sshKeyslistOf strYubikey keysSSH public keys for the primary user
machines.timeZonestr"Asia/Kolkata"System timezone
machines.localestr"en_IN"System default locale
machines.vault.addressstr"http://endeavour:8200"Vault server address
machines.monitoring.vmHostnullOr str"endeavour"Host running VictoriaMetrics
machines.serviceTarget.namestr"kedi"Systemd target name for grouped services

Home-manager options

Declared in modules/home/options.nix.

OptionTypeDefaultDescription
machines.usernamestr"ananth"Primary user account name

Service-level options

These are declared by the individual NixOS modules and live under my-services.* and my-scripts.*:

OptionModuleTypeDescription
my-services.cftunnelConfigcftunnelnullOr (listOf tunnel)Cloudflare tunnel configs
my-services.tailscaleServeConfigtailscale-servenullOr attrsTailscale serve config
my-services.kediTargetsservice-targetattrsOf boolServices for the grouped target
my-services.restartUnitsservice-targetlistOf strExtra units for the target
my-services.restartUnitsExcludeservice-targetlistOf strUnits to exclude from the target
my-services.rclone-syncsrclone-syncattrsOf syncJobRclone sync job definitions
my-services.mkBackupServicescriptsfunctionTo attrsKopia backup service builder
my-scripts.victoriaMetricsHostscriptsnullOr strVictoriaMetrics host for metrics
my-scripts.shell-helpersscriptspackageSourceable shell helpers script

Dev Environment

The dev home-manager module is a single import that delivers a complete day-to-day environment: fish + starship + a zellij/yazi workspace via yazelix, nixvim with 30+ plugins, and the usual CLI sidekicks (atuin, bat, eza, fd, fzf, gh, glab, gpg, lazygit, ripgrep, zoxide, delta). Personal identity (git user, ssh keys, sops) lives in your own config — dev provides the tooling, not the secrets.

Terminal multiplexer (zellij via yazelix)

Yazelix wires zellij + yazi into a single workspace where the file manager and shell panes coexist. There’s no tmux anymore.

ActionDefault keybinding
Open file pickeryazelix’s y from any pane (or run y for yazi standalone)
Edit a file from yaziEnter — opens in nvim, or routes to a shared zellij nvim pane if one is open
New pane / splitzellij’s Ctrl+p then a direction key
Detach sessionCtrl+o d
Quit sessionCtrl+q

Configuration lives upstream in yazelix’s docs — the dev module just enables it with terminal = "ghostty" and binds yazi/lazygit/helix to host (non-flake) versions for speed.

Shell (fish)

Fish is enabled with empty greeting, atuin for history search, zoxide for cd, and yazelix integration. There are no fixed abbreviations baked in by the module — add your own in your personal config if you want them.

Starship prompt shows: [ssh:host] dir [git_branch][git_status] (nix) (k8s/ctx) (cmd_duration) >

  • Hostname only when SSH’d in (ssh: prefix).
  • Directory truncated to 3 segments, clamped to repo root.
  • Git status is [$ahead_behind$modified] inline with the branch.
  • Nix shell marker shows when you’re inside nix develop / nix shell.
  • Kubernetes context/namespace, when the current dir matches a kube-related path.
  • Command duration only for commands ≥ 2s.
  • Prompt char is > — green on success, red on the last non-zero exit.

File manager (yazi)

Yazi opens via y (shell wrapper) or from within yazelix. Defaults: hidden files visible, natural sort, directories first.

Press Enter on a text-ish file and it opens in nvim — or hands off to an existing zellij nvim pane if one’s already running (the rule is in mgr.opener.edit).

Neovim (nixvim)

Leader key is Space. The full plugin and LSP set is in modules/home/dev.nix — the highlights below.

File navigation

KeyAction
-Open parent directory (oil.nvim file explorer)
qClose oil buffer
Ctrl+sOpen file in vertical split (in oil)
Space ffFind files (telescope)
Space flLive grep across project
Space fBSwitch buffers
Space foRecent files
Space fbFile browser (telescope)
Space f/Fuzzy find in current buffer
Space f<CR>Resume last telescope search

Harpoon (quick file switching)

Pin up to 4 files for instant access:

KeyAction
Space haAdd current file to harpoon
Space hhToggle harpoon quick menu
Space h1Space h4Jump to harpoon file 1–4

Flash (jump anywhere)

Press s then type 2 characters to jump to any visible match. Labels appear on matches — type the label to jump.

LSP

KeyAction
Space gdGo to definition
Space grList references
Space gIGo to implementation
Space gtGo to type definition
Space fsDocument symbols
Space fdDiagnostics

Diagnostics float automatically on CursorHold. Inlay hints are enabled.

Enabled language servers: bash, C/C++ (clangd), CSS, Docker, Go, HTML, LaTeX (ltex), Lua, Markdown, Nix (nil), Python (pyright), Rust (rust-analyzer), Terraform, TypeScript, YAML, Zig.

Treesitter text objects

Select, move between, and swap code structures.

Selection (visual mode):

KeySelects
vaf / vifAround/inside function
vac / vicAround/inside class
vaa / viaAround/inside parameter
vai / viiAround/inside conditional
val / vilAround/inside loop

Movement:

KeyJumps to
]f / [fNext/previous function start
]F / [FNext/previous function end
]c / [cNext/previous class start
]a / [aNext/previous parameter

Swap:

KeyAction
Space saSwap parameter with next
Space sASwap parameter with previous

Surround

KeyActionExample
ys{motion}{char}Add surroundysiw" wraps word in quotes
cs{old}{new}Change surroundcs"' changes " to '
ds{char}Delete surroundds" removes surrounding quotes

Other features

KeyAction
Space uToggle undotree (persistent undo history across sessions)
Space fqQuickfix list
Space frRegisters
Space f'Marks
Space fhHelp tags

Formatting on save

Conform-nvim with LSP fallback:

LanguageFormatters
Gogofmt, goimports
Pythonisort, black
Terraform/HCLterraform_fmt, hclfmt
Nixalejandra
Luastylua
YAMLyamlfix
Othertrim_whitespace

Copilot

GitHub Copilot is wired through copilot-cmp (completion menu), not ghost text. Suggestions appear alongside LSP completions.

Git

Configured with delta for side-by-side diffs, absorb, histogram diff algorithm, zdiff3 merge style, and rerere. The module does not set user.name or user.email — add those in your own config:

programs.git.settings.user = {
  name = "Your Name";
  email = "you@example.com";
};