Application Settings and Preferences
Application Settings and Preferences
CLIs are not the only thing in $HOME. Terminals, editors, and browsers all grow JSON/TOML/dconf trees. The boring default is: use a Home Manager module when one exists; generate the native file from Nix; do not manage files the app rewrites on every launch.
Mental model
Home Manager modules compile Nix into the format the app already understands:
| Module | Writes |
|---|---|
programs.alacritty |
~/.config/alacritty/alacritty.toml |
programs.kitty |
~/.config/kitty/kitty.conf |
programs.vscode |
settings.json + extensions from nixpkgs |
programs.ssh |
~/.ssh/config |
programs.tmux |
~/.config/tmux/tmux.conf |
If there is no module, xdg.configFile."app/config".text is still better than a README. If the app treats the config as a database (window geometry, crash dumps, “recent files”), leave it unmanaged. Home Manager 26.05 will fail the switch when it tries to overwrite a file the app rewrote, unless you set home-manager.backupFileExtension (NixOS module) or stop managing that path.
Skip: browser profiles, ~/.config/Code/User/globalStorage, ~/.local/share/*/state, anything SQLite. Manage: the TOML/JSON the app documents as configuration.
Worked examples
Case 1: Alacritty
Save as apps.nix:
# apps.nix
{ pkgs, ... }:
{
programs.alacritty = {
enable = true;
settings = {
window.opacity = 0.95;
font.size = 12.0;
font.normal.family = "JetBrains Mono";
colors.primary.background = "#1e1e1e";
};
};
}home-manager switch
test -f ~/.config/alacritty/alacritty.toml && echo okOutput:
ok
Open Alacritty. Opacity and font come from this generation. A distro-packaged Alacritty that ignores XDG is a different binary — install Alacritty via the same Home Manager module (programs.alacritty.enable already installs the package).
Case 2: SSH client config
# ssh.nix
{
programs.ssh = {
enable = true;
# Home Manager 26.05: RFC 42 programs.ssh.settings is the new form.
# matchBlocks still works; it is deprecated and migrated automatically.
extraConfig = ''
Host *
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
'';
matchBlocks = {
"desk-bastion" = {
hostname = "bastion.desk.internal";
user = "deskadmin";
port = 22;
};
"desk-*" = {
proxyJump = "desk-bastion";
user = "deskadmin";
};
};
};
}Host keys and private keys are not in this file. IdentityFile points at ~/.ssh/id_ed25519, which you create with ssh-keygen or decrypt with sops. Putting a private key in .text copies it into /nix/store.
When you migrate, programs.ssh.settings is the 26.05 knob (same ideas: Host, HostName, User). Do not invent a second ~/.ssh/config via home.file while the module is enabled — two writers, one file.
Case 3: tmux
# tmux.nix
{ pkgs, ... }:
{
programs.tmux = {
enable = true;
clock24 = true;
keyMode = "vi";
terminal = "tmux-256color";
extraConfig = ''
bind r source-file ~/.config/tmux/tmux.conf
'';
plugins = with pkgs.tmuxPlugins; [ sensible yank ];
};
}Plugins are store paths, same story as neovim.
Case 4: VS Code extensions from nixpkgs
# vscode.nix
{ pkgs, ... }:
{
programs.vscode = {
enable = true;
# 26.05: extensions and userSettings live on the profile.
# Top-level userSettings is obsolete (renamed to profiles.default.*).
profiles.default = {
extensions = with pkgs.vscode-extensions; [
jnoortheen.nix-ide
tamasfe.even-better-toml
];
userSettings = {
"editor.tabSize" = 2;
"files.insertFinalNewline" = true;
};
};
};
}Marketplace extensions that are not in nixpkgs will not appear here. That is inconvenient and correct: an unsigned VSIX from the internet is not a pinned input. Use pkgs.vscode-utils.extensionFromVscodeMarketplace only when you pin sha256.
VS Code forks on Home Manager 26.05 have their own modules (programs.vscodium, programs.cursor, …). Do not set programs.vscode.package = pkgs.vscodium — that pname knob was removed.
Do not manage ~/.config/Code/User/globalStorage or workspaceStorage. Those directories are rewritten on every launch. If home-manager switch errors … exists but is not a symlink, either backupFileExtension = "bak" once, or remove the path from Home Manager. Do not force = true on a database the editor holds open.
Case 5: Fonts the terminal named
# fonts.nix
{ pkgs, ... }:
{
fonts.fontconfig.enable = true;
home.packages = with pkgs; [
jetbrains-mono
nerd-fonts.jetbrains-mono
];
}programs.alacritty.settings.font.normal.family = "JetBrains Mono"; does nothing if the font is not installed. Put the font in home.packages (or NixOS fonts.packages).
The trap
The trap is managing ~/.config/Code/User/globalStorage or a browser profile. Those directories are mutable state. Home Manager will either clobber your session or fail the switch.
The other trap is committing programs.ssh.extraConfig with IdentityFile /nix/store/…-id_ed25519. That store path is world-readable. Keys live outside the store, or in a sops-decrypted tmpfs.
A third: programs.vscode.userSettings on 26.05 (obsolete — use profiles.default). A fourth: force = true on a file the app rewrites so every switch fights the running editor.
The boring rule
- Modules first,
xdg.configFilesecond, unmanaged mutable state last. - Terminal + font + editor settings are code. Window geometry,
globalStorage, browser profiles are not. - SSH config is code (
settings/ migratedmatchBlocks). SSH keys are not. - VS Code:
profiles.defaulton 26.05; extensions from nixpkgs (or a pinned marketplace fetch). Forks get their own module. - If
home-manager switchfights the app, stop managing that path.backupFileExtensionis a one-shot, not a lifestyle.
Try this
- Set Alacritty
font.sizeto14.0, switch, open a new terminal, then set it back. - Add an SSH
matchBlocksentry for a lab host andssh -G desk-bastion | grep hostname. readlink -f ~/.config/alacritty/alacritty.toml— store path.- Intentionally add a private key as
home.file.".ssh/id_ed25519".text = "-----BEGIN …";in a throwaway VM,nix-store -q --hashthat path, then delete it and GC. That hash is why we never do this. - Launch Alacritty, change opacity in the GUI if it has one,
switchagain. If HM fights, that file is not yours — drop it from the module.