hnm — HackerOS Nix Manager
Install Nix packages without the heavy NixOS setup.
Zero environments, zero flakes — just fast, clean package management
powered by nix-env with an HNM profile.
$ hnm install ripgrep bat htop
.hk format, and several flags that used to be accepted but
silently ignored (--no-env, --force,
-i, and a new --yes on hnm env)
now do real work. See Changelog for the full list.
Overview #
HNM gives you access to the entire nixpkgs repository
(~100 000 packages) without having to manage Nix flakes, NixOS modules,
or configuration.nix. It works identically to how
Flox
uses Nix under the hood — a dedicated nix-env profile
at ~/.hnm/profile.
nixpkgs access
All ~100 000 packages from nixpkgs-unstable, searchable instantly, descriptions included.
No flakes / NixOS
Uses plain nix-env — no heavy system setup required.
Generations
Every change creates a new generation. Roll back with one command; state resyncs automatically.
Version pinning
Pin packages to prevent unwanted upgrades — enforced by both update and remove.
Doctor
Built-in diagnostics, including state-vs-profile drift detection.
Auto-bootstrap
HNM installs Nix automatically if it's missing — just run any command.
.hk config
Settings live in config.hk, parsed with the hk-parser crate.
Auto-GC / pruning
auto_gc and max_generations in config.hk are enforced automatically.
Quickstart #
# Search for a package $ hnm search ripgrep # Install packages $ hnm install ripgrep bat htop # Show information about a package $ hnm info ripgrep # List packages installed via hnm $ hnm list -i # Browse the full local package catalog $ hnm list # Update everything $ hnm update # Remove a package (asks to confirm unless --force) $ hnm remove htop # Check Nix installation $ hnm check # Run full diagnostics $ hnm doctor
Command Reference #
Package commands
| Command | Description |
|---|---|
| hnm search <query> [--json] | Search the local nixpkgs index, including descriptions |
| hnm install <pkg...> [--no-env] | Install one or more packages. --no-env skips the PATH activation hint printed at the end |
| hnm remove <pkg...> [-f] | Remove one or more installed packages. Without -f/--force: pinned packages are skipped and you get a confirmation prompt |
| hnm update [pkg...] | Refresh the nixpkgs channel, rebuild the local index, and upgrade non-pinned packages |
| hnm upgrade | Self-update HNM from GitHub releases |
| hnm info <pkg> | Show detailed information about a package (live query) |
| hnm list [-i] [--json] | Without -i: browse the full local package catalog. With -i/--installed: only packages tracked by hnm |
| hnm which <pkg> | Show the binary path of an installed package |
| hnm pin <pkg> [version] | Pin a package to prevent it from being upgraded or casually removed |
| hnm unpin <pkg> | Remove pin from a package |
| hnm rollback [gen] | Roll back to a previous generation; resyncs tracked state automatically |
| hnm gc | Run Nix garbage collection to free store space |
System commands
| Command | Description |
|---|---|
| hnm unpack | Bootstrap Nix: install, source profile, add nixpkgs-unstable channel, patch shell rc files |
| hnm check | Verify Nix installation — prints versions of nix and nix-env |
| hnm doctor | Full system diagnostics — Nix binaries, channels, profile, PATH, and state-vs-profile drift |
| hnm env activate [-y] | Print (default) or automatically apply (--yes) the shell PATH changes |
| hnm env deactivate [-y] | Print (default) or automatically remove (--yes) that same block |
| hnm env status | Show current environment status, including which rc files are already patched |
| hnm clean | Remove HNM download cache and temporary files |
| hnm version | Show HNM and Nix version information |
| hnm help | Show command reference |
Command aliases
| Alias | Full command |
|---|---|
| hnm i | hnm install |
| hnm rm | hnm remove |
| hnm uninstall | hnm remove |
| hnm up | hnm update |
| hnm ls | hnm list |
Search & Install #
Searching
hnm search looks up the local index (pkgdb.tsv, built by
hnm update) and displays results in a formatted table, including
descriptions. Installed packages are marked with a ✓.
/ [████████████████████████████████████████] 100% done
▸ Search 'ripgrep' (3 results)
────────────────────────────────────────────────────────────────────────────────
PACKAGE VERSION DESCRIPTION
────────────────────────────────────────────────────────────────────────────────
ripgrep 14.1.1 Fast grep alternative in Rust
ripgrep-all 0.9.6 Ripgrep, but also search PDFs etc
ugrep 6.1.0 Ultra fast grep with regex
hnm install <package> to install
hnm info <package> for details
# Basic search $ hnm search ripgrep # JSON output (useful for scripting) $ hnm search ripgrep --json
nix-env -qaP --description), so hnm search
no longer shows a permanently blank description field. hnm info <pkg>
still does a separate live query for homepage/license, which the bulk index doesn't store.
Installing
hnm install resolves the package in nixpkgs, installs it into
~/.hnm/profile via nix-env, and records it — including
its description — in the HNM state file. Multiple packages can be installed in
one command.
──────────────────────────────────
[1/2] installing ripgrep
│ nix-env --profile ~/.hnm/profile --install --attr nixpkgs#ripgrep
│ resolving nixpkgs#ripgrep...
│ fetching /nix/store/...-ripgrep-14.1.1
\ [████████████████████░░░░░░░░░░░░░░░░░░░░] 52% fetching ripgrep
│ unpacking...
| [████████████████████████████████████████] 100% ✓ ripgrep 14.1.1
[2/2] installing bat
│ nix-env --profile ~/.hnm/profile --install --attr nixpkgs#bat
- [████████████████████████████████████████] 100% ✓ bat 0.24.0
✓ all packages installed successfully
# Install a single package $ hnm install ripgrep # Install multiple packages at once $ hnm install ripgrep bat htop neovim fd # Install without the PATH-activation hint at the end $ hnm install ripgrep --no-env
--no-env is meant for scripted or non-interactive installs where the
caller already manages PATH itself. It only suppresses the printed
hint — the package is still installed into the HNM profile exactly the same way.
Updating Packages #
hnm update runs three steps: refresh the nixpkgs channel
(nix-channel --update), rebuild the local search index
(nix-env -qaP --description), then upgrade installed, non-pinned
packages one at a time (nix-env -uA).
# Update everything $ hnm update # Update specific packages only $ hnm update ripgrep bat
After a successful update, if config.hk has max_generations
set (default 10), older generations beyond that count are pruned with
nix-env --delete-generations. If auto_gc is enabled,
hnm gc runs automatically afterwards. See
Configuration.
Pinning #
Pinning records a specific version in the HNM state file and marks the package so it's protected from both accidental upgrades and accidental removal.
# Pin at the currently installed version $ hnm pin ripgrep # Pin at a specific version $ hnm pin ripgrep 14.0.0 # Remove the pin $ hnm unpin ripgrep
Pinned packages appear in hnm list -i with a [pinned: x.x.x] annotation.
hnm update partitions the
installed set before upgrading and skips pinned packages entirely (they
never reach nix-env -uA). hnm remove skips pinned
packages too, unless you pass --force. A pin is a real guard on
both operations, not just a tracking label.
Generations & Rollback #
Every install, remove, or upgrade in nix-env creates a new generation —
a snapshot of the profile state. HNM exposes this through hnm rollback,
tracks the current generation number in state.json, and (if
max_generations is configured) prunes old ones automatically.
# Roll back to the previous generation $ hnm rollback # Roll back to a specific generation number $ hnm rollback 3 # The rollback command shows available generations first: gen 1 2024-12-01 gen 2 2025-01-15 gen 3 2025-03-22 ← current
state.json on its
own. hnm rollback now calls the same reconciliation
hnm list -i uses right after switching, so the tracked package list
(and the recorded generation number) doesn't go stale. See
State File & Drift.
Progress & Logs #
HNM uses a combined progress display: scrolling log lines above a live progress bar with a spinner, percentage, and current action label. Stderr from Nix commands is shown inline as error lines.
│ these 3 paths will be fetched (12.4 MiB download, 48.2 MiB unpacked):
│ /nix/store/abc123-neovim-0.10.2
│ /nix/store/def456-libuv-1.48.0
│ /nix/store/ghi789-luajit-2.1.0
│ copying path '/nix/store/def456-libuv-1.48.0' from 'https://cache.nixos.org'...
│ ERR warning: ignoring unknown setting 'experimental-features'
│ copying path '/nix/store/ghi789-luajit-2.1.0' from 'https://cache.nixos.org'...
│ copying path '/nix/store/abc123-neovim-0.10.2' from 'https://cache.nixos.org'...
/ [██████████████████████████░░░░░░░░░░░░░░] 65% installing neovim
The spinner cycles through / → - → \ → |.
Log lines scroll above the bar and are preserved in the terminal history once the
operation completes.
Color Scheme #
| Output type | Color | Style | Example |
|---|---|---|---|
| Errors | Red | Bold + Underline | ERROR cannot install 'xyz' |
| Warnings | Yellow | Underline | WARN package already installed |
| Primary (dominant) | Bright Cyan | Bold | ✓ ripgrep 14.1.1 installed |
| Secondary (less dominant) | Cyan | Normal / Dimmed | · resolving package... |
| Confirmation prompt | Yellow | Bold | ? Proceed? [y/N] |
Doctor #
hnm doctor runs a full health check of your HNM and Nix setup.
It checks for required binaries, Nix channels, the HNM profile directory,
the Nix store, state and config files, PATH configuration, and — since v0.2 —
whether state.json has drifted from what nix-env
actually has installed.
$ hnm doctor ▸ HNM Doctor — system diagnostics ────────────────────────────────────────────────── ✓ nix nix (Nix) 2.18.4 ✓ nix-env nix-env (Nix) 2.18.4 ✓ nix-channel nix-channel (Nix) 2.18.4 ✓ nix-store nix-store (Nix) 2.18.4 ✓ channels nixpkgs https://nixos.org/channels/nixpkgs-unstable ✓ profile /home/user/.hnm/profile ✓ /nix/store exists (4.2G) ✓ state 12 package(s) tracked ✓ config channel = https://nixos.org/channels/nixpkgs-unstable ✓ PATH contains .nix-profile ✓ all checks passed — HNM is healthy
If state and the real profile disagree (e.g. after a manual nix-env call, or before hnm list -i has had a chance to resync), doctor reports it instead of silently passing:
⚠ state drift: 1 untracked in profile, 0 stale in state.json → run `hnm list -i`
Environment #
HNM packages are installed into ~/.hnm/profile/bin. To use them
in your shell, add the profile to your PATH — either manually, or let
hnm env do it for you.
# Print the snippet to add to your shell rc (default, non-destructive) $ hnm env activate # Actually patch ~/.bashrc, ~/.zshrc, ~/.profile automatically $ hnm env activate --yes # Check current status, including which rc files are already patched $ hnm env status # Print removal instructions (default), or apply them automatically $ hnm env deactivate $ hnm env deactivate --yes
hnm env activate/deactivate --yes
edits your shell rc files the same way hnm unpack always has
(same underlying logic, shared in one place). Without --yes,
both subcommands still just print instructions, exactly as before — nothing
changes unless you opt in.
What gets added, if you don't use --yes and prefer to paste it yourself:
# HNM — HackerOS Nix Manager
export PATH="$HOME/.hnm/profile/bin:$HOME/.nix-profile/bin:$PATH"
[ -f ~/.nix-profile/etc/profile.d/nix.sh ] && \
. ~/.nix-profile/etc/profile.d/nix.sh
Garbage Collection #
Nix keeps every version of every package in /nix/store. Over time this
can grow large. Run hnm gc to collect unreachable store paths, or set
auto_gc = true in config.hk to have hnm remove
and hnm update do it for you automatically.
$ hnm gc ▸ Nix garbage collection ──────────────────────────────── store size before 4.2G │ nix-store --gc │ finding garbage collector roots... │ deleting garbage... │ deleting '/nix/store/old-path-xyz' │ ... / [████████████████████████████████████████] 100% ✓ garbage collection complete store size after 2.1G ✓ done
Clean Cache #
hnm clean removes HNM's own download cache and the Nix evaluation cache.
It does not touch the Nix store — use hnm gc for that.
$ hnm clean ✓ removed ~/.cache/hnm (128 MB) ✓ removed nix eval cache (44 MB) ✓ freed 172 MB To free Nix store space, run: hnm gc
Auto Nix Bootstrap #
Every command that requires Nix (install, remove, search, update, gc, etc.)
checks whether Nix is present before running. If Nix is missing, HNM
automatically runs hnm unpack to bootstrap it —
no manual action required.
hnm unpack.
This is equivalent to running the official Nix single-user installer, sourcing
the profile, registering nixpkgs-unstable as the default channel, and patching
your shell rc files (the same logic hnm env activate --yes uses).
# Manually bootstrap Nix $ hnm unpack # This runs: # sh <(curl -L https://nixos.org/nix/install) --no-daemon # . ~/.nix-profile/etc/profile.d/nix.sh # nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs # nix-channel --update # (patch ~/.bashrc, ~/.zshrc, ~/.profile)
Configuration #
As of v0.2, HNM stores its configuration as
.hk — HackerOS's own configuration format —
instead of TOML: ~/.config/hnm/config.hk. Parsing and serialization
are handled by the
hk-parser
crate. The full format spec lives at
tools-docs/hk.html.
The file is created automatically on first run with sensible defaults.
# ~/.config/hnm/config.hk ! HNM configuration — HackerOS Nix Manager ! format: https://hackeros-linux-system.github.io/HackerOS-Website/tools-docs/hk.html [hnm] -> nix_channel => "https://nixos.org/channels/nixpkgs-unstable" -> profile_dir => "/home/user/.hnm/profile" -> auto_gc => false -> max_generations => 10
| Key | Default | Description |
|---|---|---|
| nix_channel | nixpkgs-unstable URL | Nix channel to use for package resolution |
| profile_dir | ~/.hnm/profile | Path to the HNM nix-env profile |
| auto_gc | false | Run nix-store --gc automatically after hnm remove / hnm update |
| max_generations | 10 | Generations to keep after install/remove/update; older ones are pruned via nix-env --delete-generations. 0 disables pruning |
~/.config/hnm/config.toml
exists and config.hk doesn't yet, HNM migrates your settings to
the new format automatically the first time it runs — no manual steps needed.
State File & Drift #
HNM tracks installed packages in ~/.local/share/hnm/state.json.
This is separate from the Nix profile itself and records HNM-specific metadata
such as install timestamps, descriptions, pins, and the current generation number.
{
"installed": {
"ripgrep": {
"name": "ripgrep",
"version": "14.1.1",
"attr_path": "nixpkgs.ripgrep",
"installed_at": "2025-04-17T10:32:00Z",
"pinned": null,
"description": "Fast grep alternative written in Rust"
}
},
"generation": 4,
"last_update": "2025-04-17T10:35:00Z"
}
Why state can drift, and how HNM handles it
Because state.json is separate bookkeeping from nix-env's
own profile, the two can disagree — for example if a package is installed or
removed by hand with nix-env directly, or after hnm rollback
switches which generation is active (that changes what's actually installed
without touching state.json on its own).
hnm list -ireconciles state against the live profile before printing, adding anything untracked and dropping anything stale.hnm rollbackruns the same reconciliation immediately after switching generations, and updates the tracked generation number.hnm doctorreports drift as a warning (without silently fixing it), so you know reconciliation happened, or is needed.
HNM vs Flox #
| Feature | HNM | Flox |
|---|---|---|
| Package source | nixpkgs-unstable | nixpkgs + Flox catalog |
| Nix required | Auto-installs if missing | Auto-installs if missing |
| Environments / manifests | None — direct install | Per-environment manifest |
| Profile location | ~/.hnm/profile | ~/.local/share/flox/environments |
| Flakes | No | Yes (internally) |
| Config format | .hk | TOML manifest |
| Version pinning | Yes — enforced on update & remove | Yes (in manifest) |
| Rollback | nix-env generations, auto state resync | Flox generations |
| HackerOS integration | Built-in ✓ | No |
| Written in | Rust | Rust |
Changelog #
v0.2.0 — config migration & dead-code cleanup
A maintenance release following an internal review. No breaking CLI changes — flags that were previously accepted and silently ignored now do real work.
- Config moved from
config.tomltoconfig.hk(via hk-parser), with automatic migration of existing installs. Raises the effective minimum Rust toolchain to 1.85 (edition-2024 support needed transitively). hnm install --no-envnow actually suppresses the PATH-activation hint.hnm remove --force/-fnow actually skips a new confirmation prompt and allows removing pinned packages; without it, pinned packages are protected and you're asked to confirm.hnm listwithout-inow browses the full local package catalog instead of behaving identically to-i.hnm env activate/deactivategained-y/--yesto patch/unpatch shell rc files automatically, matching whathnm unpackalready did.- The local search index now stores descriptions (
nix-env -qaP --description), sohnm searchand catalog-modehnm listno longer show a blank description column. auto_gcandmax_generationsinconfig.hkare now enforced (previously parsed and never read).state.json'sgenerationfield is now actually kept up to date.- New state-vs-profile reconciliation (
hnm list -i,hnm rollback) fixes tracked packages going stale after a rollback;hnm doctorreports drift. - Removed the unused
reqwestdependency; de-duplicatedsplit_name_versionand shell-rc-patching logic that previously existed in two places each. - Fixed the help text still labeling
upgradeas[placeholder]despite being fully implemented.
v0.1.0 — initial release
First public release: install/search/remove/update/pin/rollback/gc, TOML config, generations via nix-env, auto Nix bootstrap.