Shell and Editor Configuration
Shell and Editor Configuration
Copying .zshrc snippets between laptops is how aliases drift. The boring default is: typed programs.* modules for the shell, prompt, git, and editor, with plugins fetched by Nix instead of a plugin manager that writes into $HOME.
Mental model
Home Manager modules know the file format. programs.git writes ~/.config/git/config. programs.starship writes ~/.config/starship.toml. You set Nix attributes; you do not maintain a parallel dotfile unless the module is missing.
| Module | Replaces |
|---|---|
programs.bash / zsh / fish |
Hand-written rc files for aliases, history, completions |
programs.starship |
Prompt theme copied from a gist |
programs.git |
git config --global |
programs.neovim / programs.vim |
Plugin managers (vim-plug curling GitHub on first launch) |
Enable the shell you actually log into. Enabling programs.zsh does not change /etc/passwd. The login shell is a NixOS users.users.deskadmin.shell = pkgs.zsh; (and programs.zsh.enable = true; on NixOS for /etc/zshrc). On a foreign distro, chsh once after Home Manager installed zsh in the profile.
programs.neovim.plugins takes nixpkgs vimPlugins, not Plug 'foo' that curls GitHub on first launch. A init.lua via xdg.configFile."nvim/init.lua" is fine when the module’s extraLuaConfig is too small — still no packer sync at runtime.
Worked examples
Case 1: Git as a module, not a ritual
Save as shell_editor.nix:
# shell_editor.nix
{ pkgs, ... }:
{
programs.git = {
enable = true;
userName = "Desk Engineer";
userEmail = "desk@corp.internal";
extraConfig = {
init.defaultBranch = "main";
pull.rebase = true;
rerere.enabled = true;
};
aliases = {
st = "status";
co = "checkout";
};
ignores = [ "result" ".direnv/" ];
};
}home-manager switch
git config --global --list | headOutput:
user.name=Desk Engineer
user.email=desk@corp.internal
init.defaultbranch=main
alias.st=status
git config --global user.email in a terminal will write a different file or fight the symlink. Stop doing that.
Case 2: Starship prompt
# starship.nix
{
programs.starship = {
enable = true;
enableBashIntegration = true;
settings = {
add_newline = false;
format = "$directory$git_branch$character";
character.success_symbol = "[>](bold green)";
};
};
programs.bash.enable = true;
}Starship’s native format is TOML. Home Manager accepts a Nix attrset and renders TOML. You do not keep a second starship.toml in git unless you source it wholesale with .source.
Case 3: Bash aliases and history
# bash.nix
{
programs.bash = {
enable = true;
historyControl = [ "ignoredups" "erasedups" ];
historySize = 10000;
shellAliases = {
ll = "ls -l";
gs = "git status";
".." = "cd ..";
};
bashrcExtra = ''
# rare exceptions that have no module option
export DESK_WINDOW=A
'';
};
}shellAliases is the boring place for aliases. bashrcExtra is an escape hatch; if it grows past twenty lines, look for a module.
Case 4: Neovim with plugins from nixpkgs
# nvim.nix
{ pkgs, ... }:
{
programs.neovim = {
enable = true;
viAlias = true;
vimAlias = true;
extraConfig = ''
set number
set relativenumber
'';
plugins = with pkgs.vimPlugins; [
vim-nix
vim-commentary
];
};
}Plugins are store paths. There is no ~/.local/share/nvim/site/pack filled by a job on first open. CI can nvim --headless +q without a network.
Case 5: Login shell versus module
On NixOS:
# in configuration.nix
users.users.deskadmin.shell = pkgs.bashInteractive;Home Manager’s programs.bash.enable = true writes ~/.bashrc. It does not change the passwd shell. If you enable programs.zsh but leave shell = pkgs.bashInteractive, you will wonder why zsh settings never run.
The trap
The trap is Oh My Zsh / a plugin manager plus Home Manager. OMZ clones repos into $HOME on the next login and overwrites what Home Manager just linked. Pick one. The boring pick is Home Manager + nixpkgs plugins.
The other trap is putting secrets in extraConfig (smtp.password, GitHub tokens). Those land in the world-readable store. Use sops-nix / agenix (secrets part) and point the module at the decrypted path.
The boring rule
- If a
programs.<name>module exists, use it. - Git identity and aliases live in
programs.git, not in a one-offgit config. - Plugins come from
pkgs.vimPlugins/pkgs.zshPlugins, not from a curl-on-login manager. - Set the login shell in NixOS (or
chshonce on a foreign distro). - No secrets in module extraConfig.
Try this
- Add
programs.git.aliases.lg = "log --oneline --graph -20";, switch, rungit lg. readlink -f ~/.config/git/configand confirm a store path.- Enable starship, open a new bash, and confirm
echo $STARSHIP_SESSION_KEYis set (or that the prompt changed). Disable and switch to compare. - List
pkgs.vimPluginsin anix repl(:l <nixpkgs>thenpkgs.vimPlugins.vim-nix) to see that the plugin is an ordinary derivation.