A unified Nix CLI that replaces several standalone tools with a single
binary. Two binaries: ekapkgs (client) and ekapkgs-serve (server).
ekapkgs combines the functionality of:
- nix — build, run, shell, develop, flake operations with integrated caching
- nixos-rebuild —
ekapkgs systemfor system configuration management with auto-rollback - home-manager —
ekapkgs homefor per-user dotfiles, packages, and services - nix-index —
ekapkgs searchfor packages, options, and file search with cached indexes - nix-prefetch / nix-init —
ekapkgs templatefor generating Nix expressions with GitHub metadata fetching - nix copy / cachix —
ekapkgs cachewith a negotiated binary cache protocol (single round trip) - nix-env / devenv —
ekapkgs envfor per-directory dev environments with shell hook activation - nix registry —
ekapkgs registryfor flake registry management - syft / trivy —
ekapkgs closure sbomfor CycloneDX SBOM generation from Nix closures - nix-ld + FHS —
ekapkgs fusefor on-demand/usr/libshared library serving via FUSE
The standard nix binary cache protocol requires ~3N HTTP requests for an
N-path closure (HEAD + GET narinfo + GET NAR, per path). ekapkgs sends
the full set of needed hashes to the server in one gRPC call and gets back
a manifest of everything available, with a topologically-sorted download plan.
The server also supports certificate-based signing (key rotation without client config changes) and serves as a drop-in nix binary cache for backward compatibility.
ekapkgs build nixpkgs#hello
ekapkgs run nixpkgs#hello
ekapkgs shell nixpkgs#hello nixpkgs#jq
ekapkgs develop # cache-aware nix develop
ekapkgs develop .#devShells.x86_64-linux.default
ekapkgs cache add mycache http://localhost:8080 # register a named cache
ekapkgs cache list # show configured caches
ekapkgs cache remove mycache # remove a cache entry
ekapkgs cache push mycache nixpkgs#hello # push using cache name
ekapkgs cache pull mycache nixpkgs#firefox # pre-fetch closure
ekapkgs cache auth login mycache --token TOKEN # save push credentials
ekapkgs cache auth token mycache --admin-token SECRET # mint token from server
ekapkgs cache auth status # show caches and auth status
The EKAPKGS_TOKEN environment variable can be used for push authentication,
which is useful in nix post-build hooks with a credentials file.
ekapkgs closure size nixpkgs#hello # size breakdown by path
ekapkgs closure why-depends nixpkgs#hello nixpkgs#glibc
ekapkgs closure diff nixpkgs#hello nixpkgs#curl
ekapkgs closure sbom nixpkgs#hello # CycloneDX 1.7 SBOM (runtime closure)
ekapkgs closure sbom nixpkgs#hello --buildtime # include build-time deps
ekapkgs closure sbom nixpkgs#hello --format csv # CSV output
ekapkgs closure sbom nixpkgs#hello -o sbom.cdx.json # write to file
ekapkgs closure sbom-diff OLD NEW # diff two closures by package
ekapkgs closure sbom-diff OLD NEW --format json # structured JSON diff
ekapkgs closure sbom-diff OLD NEW --format csv # CSV diff
Package metadata (CPE, PURL, license, description, source URLs, position)
is extracted via nix eval --apply with recursive dependency traversal,
enriching the full closure. Multi-output packages are coalesced into single
components. Each component includes nix:output_path and nix:position
properties, source distribution external references, and is typed as
application when meta.mainProgram is defined. For ekaos system closures,
additional metadata (role, provenance) comes from the embedded package
manifest.
sbom-diff compares closures by package name and reports version changes,
added/removed packages, and metadata changes including new or resolved CVEs,
license changes, and source provenance shifts. Each entry includes a PURL
identifier (when available) for downstream tooling correlation.
ekapkgs log nixpkgs#hello # show build log
ekapkgs dry-run nixpkgs#hello # build plan with cache breakdown
ekapkgs store gc # garbage collect
ekapkgs store gc --older-than 30d # delete paths older than 30 days
ekapkgs store gc --dry-run # preview what would be deleted
ekapkgs store optimize # deduplicate via hardlinks
ekapkgs store verify --all # check store integrity
ekapkgs store verify --all --repair # repair invalid paths
ekapkgs flake show # colored output tree
ekapkgs flake metadata # input dependency tree with revisions
ekapkgs flake update-diff nixpkgs # show closure diff before committing update
Manage flake registries (symbolic identifiers like nixpkgs that map to
full flake URLs):
ekapkgs registry list # show all registry entries
ekapkgs registry add nixpkgs github:NixOS/nixpkgs # add/replace entry
ekapkgs registry remove nixpkgs # remove entry
ekapkgs registry pin nixpkgs # pin to current revision
ekapkgs registry unpin nixpkgs # remove pin (restore floating)
ekapkgs registry resolve nixpkgs # show resolved URL
Replaces nixos-rebuild for local system configuration:
ekapkgs system switch # build and activate
ekapkgs system boot # add boot entry, activate on reboot
ekapkgs system test # activate without updating boot entry
ekapkgs system build # build only, print store path
ekapkgs system list-generations # list system generations
ekapkgs system rollback # roll back to previous generation
ekapkgs system rollback --dry-run # preview rollback
ekapkgs system prune-boot-entries # remove orphaned boot entries from ESP
ekapkgs system prune-boot-entries --gc # garbage collect first, then prune
ekapkgs system prune-boot-entries --dry-run
If activation fails during system switch, the previous profile is
automatically restored and re-activated.
Add packages to the system imperatively. Managed via
~/.config/ekapkgs/system-packages.toml with immediate install (via sudo)
into a dedicated nix profile at /nix/var/nix/profiles/ekapkgs-system-packages:
ekapkgs system packages add htop vim # add and install (requires sudo)
ekapkgs system packages remove htop # remove from manifest and profile
ekapkgs system packages list # list installed packages
ekapkgs system packages export -o sys.toml # export manifest
ekapkgs system packages import sys.toml # import and install
Replaces home-manager for per-user dotfiles, packages, and environment:
ekapkgs home switch # build and activate home config
ekapkgs home build # build only, print store path
ekapkgs home generations # list home generations
ekapkgs home rollback # roll back to previous generation
If activation fails during home switch, the previous generation is
automatically re-activated.
Home configuration is defined in the ekaos module system under
users.users.<name> and built via system.build.home.
Add packages to your home without editing nix configuration. Managed via
~/.config/ekapkgs/home-packages.toml with immediate install into a
dedicated nix profile at ~/.ekapkgs-packages:
ekapkgs home packages add alacritty ripgrep # add and install immediately
ekapkgs home packages remove alacritty # remove from manifest and profile
ekapkgs home packages list # list installed packages
ekapkgs home packages list --json # machine-readable output
ekapkgs home packages export # print manifest to stdout
ekapkgs home packages export -o pkgs.toml # export to file
ekapkgs home packages import pkgs.toml # import and install
ekapkgs home packages import pkgs.toml --merge # merge with existing
Packages can come from different flakes:
ekapkgs home packages add my-tool --flake github:user/repo
Per-directory package environments with automatic activation. Each
directory can have a .ekapkgs-env.toml manifest specifying packages
and flake dev shells.
When flake dev shells are configured, the shell hook spawns a child shell
with the full dev environment (environment variables, compiler flags,
pkg-config paths, etc.) pre-loaded from a cached nix print-dev-env
render. Entering the directory pushes the child shell; leaving it pops
back to the parent. The environment is pre-rendered on first use and
re-rendered automatically when manifest or flake files change.
For packages-only manifests (no [[flakes]]), the hook uses lightweight
PATH-only activation without spawning a child shell.
ekapkgs env init # create .ekapkgs-env.toml
ekapkgs env add jq ripgrep fd # add packages
ekapkgs env remove fd # remove a package
ekapkgs env list # show packages and flakes
ekapkgs env reload # rebuild profile from manifest
ekapkgs env allow # trust this environment
ekapkgs env disallow # revoke trust
Multiple flakes can be composed into a single environment. Each can specify a dev shell attribute, pinned revision, and input overrides:
ekapkgs env flake-add . # add local flake
ekapkgs env flake-add github:user/repo --devshell python
ekapkgs env flake-add github:other/tools --rev abc123
ekapkgs env flake-add github:foo/bar --override-input nixpkgs=github:NixOS/nixpkgs/nixos-24.05
ekapkgs env flake-remove github:user/repo
ekapkgs env flake-pin github:other/tools # pin to current revision
ekapkgs env flake-pin github:other/tools --rev deadbeef
Add to your shell configuration for automatic activation on cd:
# ~/.bashrc
eval "$(ekapkgs env hook bash)"
# ~/.zshrc
eval "$(ekapkgs env hook zsh)"
# ~/.config/fish/config.fish
ekapkgs env hook fish | sourceThe hook automatically activates when you enter a directory with a trusted
.ekapkgs-env.toml, deactivates when you leave, and reloads when
flake.nix, flake.lock, or the manifest changes.
version = 1
flake = "nixpkgs"
[[packages]]
name = "jq"
[[packages]]
name = "my-tool"
flake = "github:user/repo"
[[flakes]]
ref_ = "."
devshell = "default"
[[flakes]]
ref_ = "github:other/tools"
devshell = "default"
rev = "abc123def456"
[flakes.inputs]
nixpkgs = "github:NixOS/nixpkgs/nixos-24.05"Generate core-pkgs-compatible Nix package expressions from templates. Supports auto-detection from source files and GitHub URL metadata fetching with source hash prefetching.
ekapkgs template rust --stdout # print Rust template to stdout
ekapkgs template python default.nix # write Python template to file
ekapkgs template auto # auto-detect from source files
ekapkgs template go --from-url github.com/user/repo # fetch metadata from GitHub
ekapkgs template cmake --pname mylib --version 1.0.0 # override metadata
ekapkgs template meson --license lgpl21Plus # set license
Available templates: stdenv, cmake, meson, rust, go, python, auto.
Generated expressions use core-pkgs conventions: finalAttrs pattern,
tag instead of rev in fetchers, cmakeEntries/mesonEntries for
structured build options, pyproject = true with build-system for
Python, and no maintainers in meta.
Search packages, configuration options, or files. Indexes are cached locally as ZSTD-compressed JSON and auto-generated on first use.
ekapkgs search packages hello # search by name/description
ekapkgs search packages hello --json # machine-readable output
ekapkgs search options boot.loader # search configuration options
ekapkgs search files bin/hello # find which package provides a file
ekapkgs search update # regenerate indexes
ekapkgs search update --remote https://... # download pre-built indexes
Mount a FUSE filesystem at /usr/lib to serve shared libraries on-demand
from a Nix binary cache. Designed for use with nix-ld so unpatched binaries
with /usr/lib on their RPATH can transparently load Nix-provided .so
files without pre-installing packages.
When a process opens a library (e.g. libxml2.so.2), the FUSE layer
resolves the soname to a Nix package via a CI-generated index, downloads
the package if needed, and serves the file from /nix/store.
sudo ekapkgs fuse mount # mount at /usr/lib (requires root)
sudo ekapkgs fuse mount --foreground # stay in foreground (for systemd)
sudo ekapkgs fuse mount --mountpoint /opt/lib # custom mount point
ekapkgs fuse status # check if mounted
sudo ekapkgs fuse unmount # unmount
Requires a soname index (sonames.json.zst) available via index_url in
the client config. Transitive dependencies of Nix-built libraries use
per-DSO resolution caches and do not hit /usr/lib.
ekapkgs deploy .#nixosConfigurations.prod --target-host prod-server
ekapkgs deploy .#nixosConfigurations.prod --target-host prod-server --mode boot
ekapkgs deploy .#nixosConfigurations.prod --target-host prod-server --build-host builder
ekapkgs deploy .#nixosConfigurations.prod --target-host prod-server --dry-run
If remote activation fails, deploy prints the SSH command to roll back
the target host to its previous configuration.
ekapkgs doctor # check nix, store, caches, disk space
ekapkgs completions bash > ~/.bash_completion.d/ekapkgs
ekapkgs completions zsh > ~/.zsh/completions/_ekapkgs
ekapkgs completions fish > ~/.config/fish/completions/ekapkgs.fish
Config at ~/.config/ekapkgs/config.toml:
[[caches]]
name = "mycache"
url = "https://cache.ekapkgs.org"
token = "ekap_..."
priority = 10# Quick start — serve from local nix store
ekapkgs-serve --signing-key cache-key.sec --storage nix-store
# With config file
ekapkgs-serve --config /etc/ekapkgs-serve/config.toml
# Token management (offline — requires server restart)
ekapkgs-serve token create ci-main
ekapkgs-serve token create ci-pr --read-only
ekapkgs-serve token list
ekapkgs-serve token revoke ci-main
# Dynamic token minting (on a running server, no restart)
# Set EKAPKGS_ADMIN_TOKEN via env var or systemd EnvironmentFile
EKAPKGS_ADMIN_TOKEN=secret ekapkgs-serve --signing-key key --storage /tmp/cache
ekapkgs cache auth token mycache --admin-token secret
# Certificate signing (optional)
ekapkgs-serve generate-ca ekapkgs-root-ca-1
ekapkgs-serve issue-cert cache-2025 --ca-key ekapkgs-root-ca-1.sec --ca-name ekapkgs-root-ca-1
Server config:
[server]
bind = "0.0.0.0:8080"
[storage]
backend = "filesystem" # or "nix-store" or "castore"
path = "/var/cache/ekapkgs"
[storage.gc]
max_size = "50GiB"
gc_interval_secs = 300
[signing]
secret_key_file = "/etc/ekapkgs-serve/cache-key.sec"
[signing.certificate]
cert_file = "/etc/ekapkgs-serve/cache-2025.cert.json"
private_key_file = "/etc/ekapkgs-serve/cache-2025.key"
[auth]
write_tokens = ["legacy-token-if-needed"]- filesystem — reads/writes a cache directory (
{hash}.narinfo+nar/). Supports LRU garbage collection with configurable size limits. - nix-store — serves directly from
/nix/storevia the nix daemon, likenix-serve. No cache directory needed. - castore — content-addressed storage with chunk-level deduplication. Decomposes NARs into a blake3 Merkle tree of FastCDC chunks. Identical file content across store paths is stored once. Clients negotiate at the chunk level and download only data they don't already have locally. Supports LRU garbage collection. Typical storage savings: 30-50%.
The server speaks the standard nix binary cache protocol on the same port
as gRPC. Plain nix build --substituters http://your-server works unchanged.
Requires Rust 1.85+, protoc, and libfuse3-dev:
nix shell nixpkgs#gcc nixpkgs#protobuf nixpkgs#fuse3
cargo build --workspace
cargo test --workspace
crates/
ekapkgs/ # client binary
ekapkgs-serve/ # server binary
ekapkgs-protocol/ # protobuf types + cert verification (no IO)
ekapkgs-nix/ # nix CLI wrapping
ekapkgs-ui/ # logging, progress bars
proto/
ekapkgs/v1/ # canonical .proto definitions
MPL-2.0