4 05 Package Management
Nimmo edited this page 2026-05-14 07:54:37 +01:00

Chapter 5: Package Management

This chapter covers how software gets installed on your NixOS system -- the different methods, when to use each, and how to find what you need.

Three Levels of Packages

Your configuration installs packages at three distinct levels:

Level 1: System packages (all users, all hosts)

File: modules/common/system.nix

{ pkgs, ... }:

{
  environment.systemPackages = with pkgs; [
    htop
    ncdu
    git
    wget
    curl
    rsync
    ripgrep
    jq
    tmux
    vim
    just
    age
    sops
    ssh-to-age
    yubikey-manager
    # ... and more
  ];
}

Everything in environment.systemPackages here is available to every user on every host, because modules/common/system.nix is imported by both modules/desktop/environment.nix (desktop hosts) and modules/server/base.nix (server hosts).

Desktop-specific packages (Firefox, AMD GPU tools) are added in modules/desktop/environment.nix directly, not in system.nix. Firefox uses programs.firefox.enable = true; rather than a package entry — the programs module does more than just install the binary (policies, desktop entries, MIME types).

Level 2: User packages across all hosts (home-manager)

File: home/nimmo.nix

User packages for nimmo are managed by home-manager, which is configured in flake.nix and uses home/nimmo.nix:

{ config, pkgs, inputs, isServer, ... }:

{
  home.packages = with pkgs; [
    element-desktop
    trilium-desktop
    # ... plus conditional packages based on isServer
  ] ++ (lib.optionals (!isServer) [
    # desktop-only packages skipped on server hosts
  ]);
}

The isServer flag (passed from flake.nix via extraSpecialArgs) lets home/nimmo.nix install different packages on server hosts than on desktop hosts. Claude Code and other CLI tools are available everywhere; desktop launchers and GUI-only tools are skipped on headless machines.

Home-manager uses home.packages instead of users.users.<name>.packages. Both end up in the user's PATH, but home-manager provides additional capabilities like managing dotfiles, shell configuration, and program-specific settings.

The file home/nimmo.nix is the primary location for nimmo's user packages.

Level 3: Host-specific user packages

File: Varies by scope

For packages specific to certain hosts, they are managed via capability profiles:

# In modules/profiles/gaming.nix
home-manager.users.${user}.home.packages = with pkgs; [
  discord
  mangohud
  powertop
];

These packages are only available on hosts that import the gaming profile. Discord and MangoHud do not get installed on lena.

For packages on a single host only, they go in that host's default.nix (e.g., hosts/lena/default.nix).

How user packages merge

Nimmo's packages come from multiple sources that all end up in PATH:

  • Home-manager profiles (e.g., modules/profiles/ai-desktop.nix, modules/profiles/gaming.nix) — installed on hosts that import the profile
  • Desktop apps (modules/desktop/apps.nix) — installed on all desktop hosts
  • System packages (modules/common/system.nix) — available to all users on all hosts

On electra, nimmo gets gaming + AI + maker profile packages. On lena, nimmo gets ai-desktop + maker. On vega (server), nimmo gets ai-agents only (no desktop launchers). Package overlap is fine — lists are concatenated and deduplicated.

Profiles

Location: modules/profiles/

Profiles are curated groupings of packages and services for a particular class of workload. They are regular modules imported by host configurations.

Profile Purpose Used by
ai-agents.nix Claude Code, Codex CLI, nixd LSP, Claude Code MCP plugin (nixos), credentials vega; also imported by ai-desktop
ai-desktop.nix Imports ai-agents + opencode + desktop launchers, SOPS-backed OpenRouter credentials, host-specific Ollama providers electra, lena
gaming.nix Steam, GameMode, MangoHud, Discord electra igpu + dgpu
maker.nix 3D printing, electronics, CAD electra (all tiers), lena

The ai-desktop profile imports ai-agents internally, so importing ai-desktop gives you both the CLI tools and the opencode desktop launcher. Hosts that only want CLI tools without opencode and desktop launchers (e.g., vega) import ai-agents directly.

The former ai-development and ai-tools profiles have been consolidated: ai-desktop covers what both did for desktop machines, and ai-agents covers the subset needed on servers.

Programs vs. Packages

Some software has both a programs.<name>.enable option and a package in nixpkgs. There is an important difference:

Using programs.*.enable

programs.firefox.enable = true;
programs.steam.enable = true;
programs.gamemode.enable = true;

The programs module does more than just install the package:

  • Firefox: Sets up policies, creates desktop entries, configures MIME types
  • Steam: Enables 32-bit support, sets up the sandbox, configures pressure-vessel
  • GameMode: Installs the daemon, sets up polkit rules, creates the system service

Always check if a programs.<name>.enable option exists before adding a package manually. You can search at https://search.nixos.org/options.

Using environment.systemPackages

environment.systemPackages = with pkgs; [
  htop
  ripgrep
  wget
];

This just installs the package binary. No extra system integration. Use this for simple tools that do not need special NixOS configuration.

Desktop Applications

File: modules/desktop/apps.nix

High-level desktop applications available on all desktop/laptop hosts:

# Enable nix-ld so pre-built desktop tooling and language servers can run
programs.nix-ld.enable = true;

environment.systemPackages = with pkgs; [
  kdePackages.kate          # Text editor
  libreoffice-qt6-fresh     # Office suite
  kdePackages.okular        # Document viewer
  kdePackages.kcalc         # Calculator
  vlc                       # Media player
  kdePackages.gwenview      # Image viewer
  kdePackages.spectacle     # Screenshots
  nextcloud-client          # Cloud sync
  bitwarden-desktop         # Password manager
  element-desktop           # Matrix client
  trilium-desktop           # Notes
];

Both electra and lena import this module.

External Flake Packages

Some packages come from external flakes rather than nixpkgs. Your configuration uses several.

claude-code-nix

Declared as an input in flake.nix:

inputs = {
  claude-code-nix.url = "github:sadjow/claude-code-nix";
  claude-code-nix.inputs.nixpkgs.follows = "nixpkgs";
};

Used in a profile module (e.g., modules/profiles/ai-agents.nix) or directly in home/nimmo.nix:

{ config, pkgs, inputs, ... }:

{
  home.packages = [
    inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
  ];
}

The pattern is always:

inputs.<flake-name>.packages.${pkgs.stdenv.hostPlatform.system}.<package-name>

Breaking this down:

  • inputs.<flake-name> — the flake input
  • .packages — the packages output of that flake
  • .${pkgs.stdenv.hostPlatform.system} — the platform (e.g., x86_64-linux)
  • .<package-name> — usually default, but can be a specific name like claude-code

Note that you cannot use with pkgs; for external flake packages because they are not in pkgs. You must use the full inputs. path.

nur (Nix User Repository)

The NUR provides community-maintained packages not in nixpkgs. Your configuration uses it for declarative Firefox extension management in home/nimmo.nix:

programs.firefox.profiles.default.extensions.packages =
  with inputs.nur.legacyPackages.${pkgs.stdenv.hostPlatform.system}.repos.rycee.firefox-addons; [
    bitwarden
    consent-o-matic
  ];

Browse available Firefox addons at: https://nur.nix-community.org/repos/rycee/

Custom Desktop Entries

You can create desktop launchers for applications using makeDesktopItem. In home/nimmo.nix:

home.packages = with pkgs; [
  (makeDesktopItem {
    name = "claude-code";
    desktopName = "Claude Code";
    exec = "konsole -e claude";
    icon = "utilities-terminal";
    categories = [ "Development" ];
    terminal = false;
  })
];

This creates a .desktop file so the application appears in your KDE application menu.

Finding Packages

The fastest way: https://search.nixos.org/packages

Type a name, see available packages with their attribute names, versions, and descriptions.

# Search for a package
nix search nixpkgs firefox

# Detailed JSON output
nix search nixpkgs --json firefox | jq

# Check exact version
nix eval nixpkgs#firefox.version

Finding the right attribute name

Sometimes the package name differs from the binary name:

What you want Package attribute
btop btop
MangoHud mangohud
LibreOffice libreoffice-qt6-fresh
VLC vlc

When in doubt, search on https://search.nixos.org/packages.

The nixos-hardware Flake

The laptop hosts use nixos-hardware modules for vendor-specific optimizations:

# electra (hosts/electra/default.nix)
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series

# lena (hosts/lena/default.nix)
inputs.nixos-hardware.nixosModules.lenovo-ideapad-16ahp9

These modules apply optimizations specific to the hardware — kernel parameters, power management, driver selection. Vega does not use a nixos-hardware module (generic server hardware). Browse available hardware modules at: https://github.com/NixOS/nixos-hardware

Unfree Packages

Some packages (Steam, Discord, NVIDIA drivers) are not free software. NixOS does not install them by default. Your configuration allows them globally:

# In hosts/common/nix-settings.nix
nixpkgs.config.allowUnfree = true;

Without this line, any unfree package would cause a build error.

Binary Caches

Building packages from source is slow. Binary caches provide pre-built packages. Your configuration uses several:

# In hosts/common/nix-settings.nix
substituters = [
  "https://nix-cache.nimmog.uk"
  "https://claude-code.cachix.org"
  "https://cache.nixos.org"
];
  • cache.nixos.org — the official NixOS cache, has most packages
  • claude-code.cachix.org — a third-party cache for the Claude Code package
  • nix-cache.nimmog.uk — a self-hosted nix binary cache that pre-builds all flake configurations

When Nix needs a package, it checks the caches first. If a pre-built binary exists, it downloads it (fast). Otherwise, it builds from source (slow). The self-hosted binary cache means that after a push to git, electra and lena can typically download pre-built packages rather than building from source.

Package Versioning

On NixOS, you do not choose individual package versions. The version of every package is determined by which revision of nixpkgs you are using (pinned in flake.lock).

When you run nix flake update, you get the latest versions of everything. This is different from apt/pacman where you update packages individually.

If you need a specific version of a package, you would need to use an overlay or pin a specific nixpkgs revision. This is an advanced topic not currently used in your configuration.