Packages
Package Ecosystem & Customization
A multi-layered package management strategy combining standard Nixpkgs with local extensions and overlays.
📦 The Package Hierarchy
This repository manages packages through three distinct layers:
1. Nixpkgs (The Base)
We primarily track nixos-unstable for the latest hardware support and software features. Standard packages are accessed via the global pkgs attribute.
2. Local Packages (hey.packages)
Custom derivations located in the packages/ directory are automatically scanned and exposed under the hey.packages namespace.
- Exposure: Handled by
flake.nixandlib/nixos.nix. - Purpose: Ideal for small utilities (like
betterfox) that aren’t in upstream Nixpkgs or require specialized versions. - Usage: Always prefer
hey.packages.namefor files inside this repo to ensure reproducibility.
3. Overlays (overlays/)
Overlays allow us to modify existing packages in Nixpkgs globally.
Static Overlays (directory-based)
Every file in overlays/ is automatically imported and applied to the pkgs set
via mapModules ./overlays import in flake.nix. Example: overriding a kernel
version or fixing a broken library across all applications.
Module-Level Overlays (nixpkgs.overlays)
For overlays tied to a specific feature module, use the NixOS nixpkgs.overlays
option directly in the module config — no need to touch flake.nix:
# modules/dev/rust.nix
nixpkgs.overlays = [
hey.inputs.rust-overlay.overlays.default
];
# modules/editors/default.nix
nixpkgs.overlays = [
hey.inputs.nix-vscode-extensions.overlays.default
];
This pattern keeps the overlay self-contained with the module that needs it
and ensures it inherits the system nixpkgs config (e.g. allowUnfree = true).
It works even when nixpkgs.pkgs is externally provided — NixOS applies
module-level overlays via pkgs.appendOverlays.
🛠️ Custom Packages Logic
The mapModules Pipeline
We use a custom library function to auto-load packages:
packages = mapModules ./packages import;
This means adding a new package is as simple as creating a folder in packages/ with a default.nix.
Versions & Determinism
For local packages that fetch from GitHub (like betterfox), we use fetchFromGitHub with explicit commit hashes and sha256 SRI hashes. This guarantees that your environment stays identical regardless of when or where it is built.
🏗️ Stable vs. Unstable
Our flake includes a nixpkgs-stable input. In rare cases where a package is broken on unstable, we can access the stable version via pkgs.pkgs-stable.<name>. This is implemented as a global overlay in lib/nixos.nix.
🛠️ Developer: Local Package Lifecycle
When adding or updating a package in packages/, follow this “Surgical Iteration” workflow to avoid slow rebuilds.
1. Isolated Build Test
Don’t run hey sync to test a package change. Build it in isolation using a temporary expression:
nix-build -E 'with import <nixpkgs> {}; callPackage ./packages/your-pkg/default.nix { self = {}; }'
Note: If the package requires specific overrides (like rofi-wayland), pass them in the attribute set.
2. Idiomatic Patching: substituteInPlace
Avoid using .patch files for simple path redirections. Patches are brittle and break when upstream line numbers change. Use substituteInPlace in the postPatch phase:
postPatch = ''
substituteInPlace configure.ac \
--replace-fail '[PLUGIN_DIR]="`$PKG_CONFIG --variable=pluginsdir rofi`"' \
'[PLUGIN_DIR]="$out/lib/rofi"'
'';
This is “Search & Replace” for your source code, making it resilient to version bumps.
3. Pruning Broken Upstream Logic
If an upstream update breaks secondary components (like a test suite or documentation generator), don’t revert. Prune it in prePatch:
prePatch = ''
# Skip building broken tests
sed -i 's/SUBDIRS = src tests/SUBDIRS = src/' Makefile.am
sed -i 's/tests\/Makefile//' configure.ac
'';
🚀 Adding a Local Package
- Create
packages/<your-pkg>/default.nix. - Write a standard Nix derivation.
- It will be immediately available as
hey.packages.<your-pkg>in any module. - If you want it available globally as just
<your-pkg>, add an overlay inoverlays/.