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 adevthat 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.
| Module | Standalone? | Dependencies |
|---|---|---|
homeManagerModules.shell | Yes | None |
homeManagerModules.dev | Yes | nixvim (provided transitively via this flake’s inputs) |
nixosModules.nix-settings | Yes | None |
nixosModules.service-target | Yes | nixosModules.options (for machines.serviceTarget.name) |
nixosModules.tailscale-serve | Yes | None |
nixosModules.rclone-sync | No | Requires nixosModules.scripts (for my-scripts.shell-helpers) |
nixosModules.scripts | No | Requires vault-secrets NixOS module (declares kopia/gcloud secrets) |
nixosModules.cftunnel | No | Requires 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 boilerplatemy-scripts.victoriaMetricsHost— host running VictoriaMetrics (forwrite_metrichelper)my-scripts.shell-helpers— sourceable shell script withdie,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 totrue)my-services.restartUnits— extra units to includemy-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.
| Option | Type | Default | Description |
|---|---|---|---|
machines.username | str | "ananth" | Primary user account name |
machines.sshKeys | listOf str | Yubikey keys | SSH public keys for the primary user |
machines.timeZone | str | "Asia/Kolkata" | System timezone |
machines.locale | str | "en_IN" | System default locale |
machines.vault.address | str | "http://endeavour:8200" | Vault server address |
machines.monitoring.vmHost | nullOr str | "endeavour" | Host running VictoriaMetrics |
machines.serviceTarget.name | str | "kedi" | Systemd target name for grouped services |
Home-manager options
Declared in modules/home/options.nix.
| Option | Type | Default | Description |
|---|---|---|---|
machines.username | str | "ananth" | Primary user account name |
Service-level options
These are declared by the individual NixOS modules and live under my-services.* and my-scripts.*:
| Option | Module | Type | Description |
|---|---|---|---|
my-services.cftunnelConfig | cftunnel | nullOr (listOf tunnel) | Cloudflare tunnel configs |
my-services.tailscaleServeConfig | tailscale-serve | nullOr attrs | Tailscale serve config |
my-services.kediTargets | service-target | attrsOf bool | Services for the grouped target |
my-services.restartUnits | service-target | listOf str | Extra units for the target |
my-services.restartUnitsExclude | service-target | listOf str | Units to exclude from the target |
my-services.rclone-syncs | rclone-sync | attrsOf syncJob | Rclone sync job definitions |
my-services.mkBackupService | scripts | functionTo attrs | Kopia backup service builder |
my-scripts.victoriaMetricsHost | scripts | nullOr str | VictoriaMetrics host for metrics |
my-scripts.shell-helpers | scripts | package | Sourceable 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.
| Action | Default keybinding |
|---|---|
| Open file picker | yazelix’s y from any pane (or run y for yazi standalone) |
| Edit a file from yazi | Enter — opens in nvim, or routes to a shared zellij nvim pane if one is open |
| New pane / split | zellij’s Ctrl+p then a direction key |
| Detach session | Ctrl+o d |
| Quit session | Ctrl+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
| Key | Action |
|---|---|
- | Open parent directory (oil.nvim file explorer) |
q | Close oil buffer |
Ctrl+s | Open file in vertical split (in oil) |
Space ff | Find files (telescope) |
Space fl | Live grep across project |
Space fB | Switch buffers |
Space fo | Recent files |
Space fb | File 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:
| Key | Action |
|---|---|
Space ha | Add current file to harpoon |
Space hh | Toggle harpoon quick menu |
Space h1–Space h4 | Jump 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
| Key | Action |
|---|---|
Space gd | Go to definition |
Space gr | List references |
Space gI | Go to implementation |
Space gt | Go to type definition |
Space fs | Document symbols |
Space fd | Diagnostics |
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):
| Key | Selects |
|---|---|
vaf / vif | Around/inside function |
vac / vic | Around/inside class |
vaa / via | Around/inside parameter |
vai / vii | Around/inside conditional |
val / vil | Around/inside loop |
Movement:
| Key | Jumps to |
|---|---|
]f / [f | Next/previous function start |
]F / [F | Next/previous function end |
]c / [c | Next/previous class start |
]a / [a | Next/previous parameter |
Swap:
| Key | Action |
|---|---|
Space sa | Swap parameter with next |
Space sA | Swap parameter with previous |
Surround
| Key | Action | Example |
|---|---|---|
ys{motion}{char} | Add surround | ysiw" wraps word in quotes |
cs{old}{new} | Change surround | cs"' changes " to ' |
ds{char} | Delete surround | ds" removes surrounding quotes |
Other features
| Key | Action |
|---|---|
Space u | Toggle undotree (persistent undo history across sessions) |
Space fq | Quickfix list |
Space fr | Registers |
Space f' | Marks |
Space fh | Help tags |
Formatting on save
Conform-nvim with LSP fallback:
| Language | Formatters |
|---|---|
| Go | gofmt, goimports |
| Python | isort, black |
| Terraform/HCL | terraform_fmt, hclfmt |
| Nix | alejandra |
| Lua | stylua |
| YAML | yamlfix |
| Other | trim_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";
};