nina

@june

NixOS is powerful, but it can be a lot. Nina keeps the whole workflow — packages, options, rebuilds, services, remote machines — right at the prompt.

https://nina.asha.software

Zig

v3.4.14 · 229 saves · 10,620 lines · updated 6 days ago

42 files
d87bd9832610
back to HEAD
kiln/8.3 KB · 4 months ago
src/130.1 KB · 4 months ago
test/24.6 KB · 4 months ago
vm/A reproducible NixOS 24.11 dev image for exercising nina, built once and booted as disposable copy-on-write overlays so every run starts from the exact same snapshot.24.6 KB · 4 months ago
tests/11.5 KB · 4 months ago
boot-overlay.shBoot a THROWAWAY copy-on-write overlay of the golden image, run commands over the serial console, then discard the overlay.2.0 KB · 4 months agobuild-golden.shBake the golden NixOS qcow2: boot the minimal ISO headless under qemu, drive a scripted nixos-install over the serial console, power off.3.5 KB · 4 months agoconfiguration.nixGolden NixOS dev image for nina testing — the reproducible "exact starting snapshot".2.4 KB · 4 months agoinstall.shRuns INSIDE the NixOS minimal installer (over serial) to lay down the golden system onto /dev/vda from the seed media.1.2 KB · 4 months agoREADME.mdA reproducible NixOS 24.11 dev image for exercising nina, built once and booted as disposable copy-on-write overlays so every run starts from the exact same snapshot.1.7 KB · 4 months agorun-in-vm.shRun a test script inside a throwaway overlay of the golden image, injecting a fresh nina binary so we test the latest build without rebaking the golden.2.3 KB · 4 months ago
build.zig1.7 KB · 4 months agobuild.zig.zon4.2 KB · 4 months agoflake.nix2.0 KB · 4 months agoinstall.shnina install script Run: curl -fsSL kepr.uk/nina/install.sh | sh Or: NINA_INSTALL_DIR=/usr/local/bin sh install.sh10.9 KB · 4 months agokepr.build211 B · 4 months agonixos-module.nixStandalone NixOS module for nina — no flake inputs needed.1.3 KB · 4 months agoVERSION7 B · 4 months ago
b4b144ca74c1rewrite Nina README around verified workflows (3.4.14)6 days ago1721e21f7a84give each koh.build platform its own output path so koh offer --builds can publish (3.4.14)1 week ago75893c079c90migrate: rename kepr.build to koh.build1 week ago1df7c5b3bc0adocs: Record multi-input pin smoke evidence3 months ago3c9ac84ad6d7fix: Resolve fable audit pin and option leftovers (v3.4.14)3 months ago36df571ddf9dfable audit fix campaign complete — all 26 findings resolved (NINA-001 through NINA-026), NINA-008 deferred to honest-failure stub and NINA-009 to best-effort pending real-host verification, see docs/fable-audit-2026-07-02-resolved.md and CONTINUITY.md for full resolution detail3 months ago28a27c6d7ba4probe /dev/tty accessibility before reading the install prompt so non-interactive contexts (CI, provisioning, curl|sh with no controlling tty) exit 0 with install instructions instead of dying on a bare redirection error (NINA-025)3 months agod598fb1dd67bbump build.zig.zon .version to match VERSION and add zon/VERSION sync-check test (NINA-018)3 months ago2b98515f8daesync stale ~/.nina.conf error strings and man/README search-key + log-path docs to current XDG config path, real search keybindings, and real log path (NINA-017/019)3 months agoa4d2b79b37cbman pager help screen's i==0 row now emits exactly one line instead of two, so the help frame is exactly PAGE_ROWS tall and the redraw's cursorUp(PAGE_ROWS + FOOTER_ROWS) no longer drifts the frame up a row on every dismiss/redraw; dismissing help with any key now forces an immediate body+footer redraw instead of leaving stale help content until the next navigation key; extends the v3.4.5 ghosting-drift regression coverage to drawHelpBody and drawBody with newline-count tests against captured output (NINA-015)3 months ago48628db291a4search/man TUI raw mode restores the terminal on SIGINT/SIGTERM/SIGHUP via a shared rawterm.zig module, instead of relying solely on defer (which never runs on a signal or a ReleaseSafe panic-abort); ISIG stays enabled so ^C now delivers SIGINT to break out of a wedged search instead of landing as a dead 0x03 byte, and the handler restores the saved termios before re-raising with the default disposition so the process still dies the way it would have (NINA-014)3 months agoceda368a6deadelete getLicense: no caller anywhere in src/ or test/ (the Tab detail pane uses packageMeta), and it returned a slice into result.stdout no caller could ever free correctly (NINA-021)3 months agod55b75fe962bsearchOptions builds a working nix eval --impure --expr invocation (nixosSystem + optionAttrSetToDocList) instead of the previous nix eval nixpkgs#lib.optionAttrSetToDocList nixpkgs#nixosModules call that could never succeed (two positionals, a non-serializable function, an unevaluable output path); filterOptions now also accepts the literalExpression-wrapped default shape alongside plain strings; best-effort fix verified only via fixture-driven parsing tests (no nix on this host) — needs real-host/VM confirmation before fully resolved (NINA-009)3 months agoe5a03023f758bootEntry prints its date suffix as a separate p() call instead of allocPrint-ing via std.heap.page_allocator inline (never freed) — same visual output, no allocation (NINA-023)3 months ago135d27fa00b4hook output, an unparseable log line, and nix search descriptions now pass through a new output.sanitize() before reaching the terminal, stripping control bytes < 0x20 (except \n/\t) and CSI sequences so embedded ESC bytes can't inject escape sequences; generalizes exec.zig's stripAnsi (removed, both diff-closures call sites now use output.sanitize) (NINA-016)3 months ago2bc90194d359output.confirm() now refuses on a closed/non-interactive stdin instead of assuming yes, printing a hint to pass --yes; adds a real --yes flag (main.zig parseArgs + output.setAutoYes) for legitimate automation; confirmDefaultNo is unchanged (NINA-005)3 months agoa801f719897clogAction JSON-escapes ctx.machine.name before embedding it in a JSONL log entry, so a config machine name containing a quote or backslash no longer produces an invalid line that renderJsonLogLine can only dump raw (NINA-020)3 months agoe923ab8ccb1aflake/channel/profile/pkg dispatchers reject an unrecognized subcommand with subcommand help and a non-zero exit instead of silently falling off the if-chain and exiting 0; adds a profile_help table (none existed before) and a shared subcommandKnown helper (NINA-012)3 months ago4f0092888eb7service allowlists start/stop/restart/enable/disable before forwarding a verb to sudo systemctl, rejecting unlisted verbs like mask/kill/revert with subcommand help and a non-zero exit instead of running them unconfirmed and undocumented (NINA-011)3 months ago721ba656a3a3pin now fails loudly with 'not implemented yet' instead of silently no-op'ing through the editFlakeLock stub and reporting fake success; unpin (which genuinely runs nix flake update) replaces pin in help/man since pin needs real flake.lock input-source resolution out of scope for a mechanical fix (NINA-008)3 months agoa2c2b67bd83adiff guards against a u32 underflow panic by returning a friendly message when no generation is marked current or too few generations exist, instead of falling into current - 1 arithmetic (NINA-007)3 months agod5b8315c6adeself-update builds the new version into the store before touching the profile, so a network drop or eval failure never leaves nina uninstalled (NINA-006)3 months ago74c9cc1a35abdocument the refAllDecls test-discovery gap and its fix in CONTINUITY.md3 months ago64b6755254d1wire refAllDecls(@This()) into main.zig so zig build test discovers test blocks in errors.zig, config.zig, exec.zig, commands.zig, and types.zig; fix leaked TranslatedError string fields in errors.zig tests and leaked config/machine-name allocations in config.zig tests that this surfaced3 months agob3f422a2f4b6commandExists passes its argument as real argv ($1) instead of interpolating it into sh -c script text (NINA-022)3 months ago92e29d901105doctor's config-syntax dry-build check drops the unnecessary sudo prefix — dry-build never activates, so it needs no more privilege than reading config (NINA-013); verified by code inspection only, no VM golden doctor test harness exists to extend3 months ago26454ed98d54runHook quotes $1 in its sh -c runner script so a hook path containing spaces or glob characters runs correctly (NINA-010)3 months agod4dd5ae4eb6eself-update download uses a random mktemp path instead of a predictable /tmp/nina-update.bin, and the caller deletes it after install (NINA-004)3 months ago08ace490d251remote (--on) execution shell-quotes argv into a single ssh command word, closing metacharacter injection on the remote shell (NINA-003)3 months ago2d2b5035db17streamed build stderr now newline-preserved and drain-safe on StreamTooLong (NINA-001/002)3 months ago9a677da5a863fable audit: read-only line-by-line review — 2026-07-023 months agobcb25ca4b195flake check: propagate nix flake check failure as a non-zero nina exit code instead of always returning 0; bump v3.4.133 months agoc1db326d2381record real-tool golden VM verification of the passthrough audit fixes in CONTINUITY.md3 months agoe1ccc4573d0fhome apply/check: thread ctx.passthrough into home-manager switch/build argv, home-manager build's shell wrapper takes extra flags via "$@" to avoid injection; bump v3.4.123 months agoc93535d54e46check: thread ctx.passthrough into flake check argv (dry-build/check argv already covered by the nixos-rebuild pass)3 months agoaf8ae2f6db79run: thread ctx.passthrough into nix run argv and stop nina's own flags matching after a literal -- separator3 months ago4c4db6e43144develop: thread ctx.passthrough into nix develop argv3 months ago67f764ba93c7build: thread ctx.passthrough into nix build argv3 months ago2bd71fb27d78apply/upgrade: thread ctx.passthrough into nixos-rebuild switch argv3 months agod00f8194f384forward unrecognized flags like --refresh from nina flake update to nix flake update; bump v3.4.113 months ago762b7aa461dfsize the man pager to the real terminal height via TIOCGWINSZ instead of a fixed 22-row guess; bump v3.4.103 months ago0feb174dd569bump VERSION to 3.4.93 months agoafce73f72872add --refresh to nix profile add in self-update so it bypasses nix's own tarball-ttl cache, not just the narHash-pinned originalUrl3 months ago3d1160fd9b1efix nina update reporting false success on nix profile installs: nix profile upgrade re-resolves the element's own originalUrl, which the --no-write-lock-file archive install bakes a permanent narHash pin into, so upgrade was silently re-fetching the exact same locked content forever (exit 0, no warning text) instead of a real update; replace it with nix profile remove + nix profile add against the plain unpinned flake URL so HEAD is genuinely re-resolved; bump v3.4.83 months agob33a68e0dd08show full version-range changelog on nina update instead of only the latest release's notes3 months agod51758b58acbenable raw terminal mode on macos for the man and search TUIs: drop the linux-only termios guard (the ECHO/ICANON/ISIG/IEXTEN/ICRNL/IXON flags and V.MIN/V.TIME exist on every POSIX target Zig exposes, so the guard was a false assumption that left macos in cooked mode — echo on, line-buffered, requiring Enter); OPOST stays on so newline-to-CR-LF translation the renderers rely on is unchanged; bump v3.4.63 months ago309cf693ce0ffix man pager ghosting on scroll and section moves (redraw climbs exactly the body+footer rows; every body row emits its newline so frames no longer drift and leave ghost rows; exit and help clear the full frame) and lift body text from website --text-mid to a light warm tone so paragraphs read on a dark terminal; bump v3.4.53 months agoa36b421dce6badd `nina man [topic]` pager with website palette and 17-section docs tree; bump v3.4.43 months agoed87fe45ff11auto-retry nix search + profile add with NIXPKGS_ALLOW_UNFREE=1 --impure when unfree error detected; bump v3.4.33 months ago12e8a2b5e5f0add nix/tamal/manifest.kdl: fresh-cmd queries kepr saves.json to pin latest nina save automatically3 months ago

older →

details

nina

Nina is a command-line helper for NixOS. Use it to find packages, apply your configuration, roll back a generation, or check another machine over SSH.

nina search ripgrep
nina apply
nina back
nina status --all

It calls the Nix tools you already use and gives common tasks shorter names. Your configuration stays in Nix files. Nina is written in Zig and builds as a single binary with no third-party Zig dependencies; it needs Nix and the relevant system tools to carry out commands.

Install

Nina's system commands are for NixOS. Package and development commands use Nix, which needs the nix-command and flakes experimental features enabled.

Install in your user profile:

nix profile add 'https://kepr.uk/nina/archive/HEAD.tar.gz#nina'

Then run:

nina setup

Setup creates Nina's configuration if it is missing and checks the required Nix features. On NixOS, it offers to add the setting to /etc/nixos/configuration.nix; you must rebuild for it to take effect. If the install command above fails because these features are disabled, enable them in Nix first.

To build from source, you need Zig 0.16:

koh steal kepr.uk/nina
cd nina && zig build -Doptimize=ReleaseFast
sudo cp zig-out/bin/nina /usr/local/bin/nina

You can also download a source archive from releases:

tar xf nina-*.tar.gz && cd nina-*/
zig build -Doptimize=ReleaseFast
sudo cp zig-out/bin/nina /usr/local/bin/nina

nina goodbye uninstalls Nina. Run nina update to check for a newer Nina release. If your system configuration manages Nina, update it through that configuration.

Apply a configuration

nina edit opens configuration.nix in your editor. Check it before applying, or build it without switching:

nina edit
nina check
nina apply --dry
nina apply

Nina also supports flake-based system configurations. Set the machine's config directory to the directory containing your configuration.

If you need to go back, use nina back. To choose a specific generation, find its number with nina history, then run nina go <n>.

Command What it does
nina history Lists system generations
nina back Rolls back one generation
nina go <n> Switches to generation <n>
nina diff Compares generations
nina upgrade Updates Nix inputs or channels and rebuilds
nina clean Removes old system generations and collects garbage

nina clean keeps the number of generations set in your config, five by default. Removing generations means you can no longer roll back to them.

Find and use packages

nina search <q> searches nixpkgs through nix search. In a terminal, results appear in an inline browser. Use the arrow keys to move, Tab for package details, Enter to select a package, and Escape to cancel. When stdin is not a terminal, search prints a plain list.

nina search ripgrep
nina install ripgrep
nina try ripgrep
nina remove ripgrep
nina list

Selecting a package in search or running nina install asks how you want to use it:

  • Install it in your user profile without a system rebuild.
  • Add it through your system configuration: Nina opens the editor at the package list and offers to apply your changes.
  • Try it in a temporary shell without installing it.

Search and installation may need network access to fetch Nix inputs or packages. nina option <q> searches NixOS options.

Home Manager

Nina supports standalone Home Manager and Home Manager integrated into a NixOS configuration. To create a standalone configuration:

nina home init

Add --switch to activate it immediately. Nina chooses a Home Manager branch from your NixOS version and leaves an existing configuration in place.

Command What it does
nina home apply Applies your home configuration
nina home back Rolls back one home generation
nina home history Lists home generations
nina home edit Opens home.nix
nina home check Builds the home configuration without switching
nina home diff Compares the latest two home generations
nina home packages Lists packages in the home generation

When Home Manager is part of your NixOS configuration, nina home apply runs a system rebuild. Home commands operate on the local user; they do not support --on.

Configure Nina

Nina reads ~/.config/nina/config, or $XDG_CONFIG_HOME/nina/config when that environment variable is set.

editor      = hx
generations = 5
confirm     = true
teach       = false
color       = true

[machine]
name    = my-machine
config  = /etc/nixos
local   = true
default = true

Set editor to your preferred editor. generations controls how many system generations cleanup keeps. With teach = true, Nina prints the underlying commands as you work.

Nina disables color when stdout is not a terminal and respects NO_COLOR.

Work on another machine

Add a machine with an SSH host to your config:

[machine]
name    = azula
host    = azula
user    = june
ssh_key = ~/.ssh/id_ed25519

Put --on <name> after the command to select it:

nina apply --on azula
nina service logs ollama -f --on azula
nina status --all

Remote system operations use SSH and the tools on the target machine. nina status --all checks every configured machine. Use nina hello to list them.

Run hooks

Place executable scripts in ~/.config/nina/hooks/ to run before or after these operations:

pre-apply     post-apply
pre-back      post-back
pre-home      post-home
pre-upgrade   post-upgrade

Missing or non-executable hooks are skipped. If a pre-hook fails, Nina shows its last five output lines and asks whether to continue, defaulting to no. A failed post-hook produces a warning after the operation succeeds. Hooks run locally, including when the operation targets a remote machine.

For example, save this as ~/.config/nina/hooks/pre-apply to check for uncommitted changes, then make it executable with chmod +x:

#!/usr/bin/env sh
# ~/.config/nina/hooks/pre-apply
if git -C ~/nixos-config status --porcelain | grep -q .; then
    echo "uncommitted changes - commit before applying"
    exit 1
fi

More commands

Command What it does
nina status Shows machine health
nina doctor Checks for common problems
nina log Shows Nina's operation history
nina service list Lists systemd services
nina service logs <s> Reads a service's journal
nina service start/stop/restart <s> Controls a service
nina flake show Shows flake outputs
nina flake update Updates flake inputs
nina pin <in> <rev> Pins a flake input to a revision
nina unpin <in> Updates a pinned input
nina develop Enters a development shell
nina build Builds a flake output
nina run <p> Runs a package
nina edit --dir Opens the configuration directory
nina fmt Formats the system configuration
nina info Shows system information
nina boot Lists boot entries

Run nina help for the full command list, nina <group> help for a group such as home or service, and nina man [topic] for the built-in manual. nina --version prints the installed version.

Build and test

From the source directory:

zig build
zig build test
zig fmt --check build.zig src/*.zig

System operations need a NixOS host for live testing. The VM test harness describes testing against disposable NixOS images.

Made by Asha Software.

Website · Source · Documentation