/ hnm
v0.2.0 · HackerOS Linux · Built-in

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
Built into HackerOS — HNM is pre-installed on every edition of HackerOS. It is designed exclusively for HackerOS and requires no manual installation.
v0.2.0 — configuration moved from TOML to HackerOS's own .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

CommandDescription
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 upgradeSelf-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 gcRun Nix garbage collection to free store space

System commands

CommandDescription
hnm unpackBootstrap Nix: install, source profile, add nixpkgs-unstable channel, patch shell rc files
hnm checkVerify Nix installation — prints versions of nix and nix-env
hnm doctorFull 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 statusShow current environment status, including which rc files are already patched
hnm cleanRemove HNM download cache and temporary files
hnm versionShow HNM and Nix version information
hnm helpShow command reference

Command aliases

AliasFull command
hnm ihnm install
hnm rmhnm remove
hnm uninstallhnm remove
hnm uphnm update
hnm lshnm 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 .

searching local index (pkgdb.tsv)...
/ [████████████████████████████████████████] 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
Since v0.2: the local index carries a description column (built with 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.

▸ Installing 2 package(s)
──────────────────────────────────

[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.

Pin enforcementhnm 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 resync on rollback — switching generations changes which packages are actually installed without touching 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.

hnm install neovim
nix-env --profile ~/.hnm/profile --install --attr nixpkgs#neovim --file <nixpkgs>
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 typeColorStyleExample
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
Since v0.2: 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.

You can also trigger the bootstrap manually at any time with 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
KeyDefaultDescription
nix_channelnixpkgs-unstable URLNix channel to use for package resolution
profile_dir~/.hnm/profilePath to the HNM nix-env profile
auto_gcfalseRun nix-store --gc automatically after hnm remove / hnm update
max_generations10Generations to keep after install/remove/update; older ones are pruned via nix-env --delete-generations. 0 disables pruning
Upgrading from v0.1? If ~/.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 vs Flox #

FeatureHNMFlox
Package sourcenixpkgs-unstablenixpkgs + Flox catalog
Nix requiredAuto-installs if missingAuto-installs if missing
Environments / manifestsNone — direct installPer-environment manifest
Profile location~/.hnm/profile~/.local/share/flox/environments
FlakesNoYes (internally)
Config format.hkTOML manifest
Version pinningYes — enforced on update & removeYes (in manifest)
Rollbacknix-env generations, auto state resyncFlox generations
HackerOS integrationBuilt-in ✓No
Written inRustRust

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.toml to config.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-env now actually suppresses the PATH-activation hint.
  • hnm remove --force/-f now actually skips a new confirmation prompt and allows removing pinned packages; without it, pinned packages are protected and you're asked to confirm.
  • hnm list without -i now browses the full local package catalog instead of behaving identically to -i.
  • hnm env activate/deactivate gained -y/--yes to patch/unpatch shell rc files automatically, matching what hnm unpack already did.
  • The local search index now stores descriptions (nix-env -qaP --description), so hnm search and catalog-mode hnm list no longer show a blank description column.
  • auto_gc and max_generations in config.hk are now enforced (previously parsed and never read).
  • state.json's generation field 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 doctor reports drift.
  • Removed the unused reqwest dependency; de-duplicated split_name_version and shell-rc-patching logic that previously existed in two places each.
  • Fixed the help text still labeling upgrade as [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.