Rust CLI that provides imperative package management UX (nex install foo) on top of declarative nix-darwin configuration. Edits .nix files, runs darwin-rebuild switch, and reverts on failure. Published as nex-pkg on crates.io; binary name is nex.
Single crate, no workspace. Key modules:
aliases.rs— package name alias table (rg->ripgrep, zed->zed-editor, ykman->yubikey-manager, etc.) with brew↔nix cross-detection for adopt/install duplicate preventioncli.rs— clap derive structs, all subcommandsconfig.rs— config resolution: CLI flags -> env vars -> config file (~/.config/nex/config.toml) -> auto-discovery; persistent preferences (prefer_nix_on_equal)discover.rs— find the nix-darwin repo and hostnameresolve.rs— multi-source resolution: checks nixpkgs (with alias lookup) + brew cask + brew formula, compares versions, recommends best source, interactive prompt with "always nix" option for equal versionsnixfile.rs—NixListmodel describing editable lists in nix files (open/close patterns, indent, quoting)edit.rs— line-based nix file editing: contains, insert, remove, list_packages, backup/restoreexec.rs— subprocess wrappers for nix (with well-known path fallback), darwin-rebuild, brew; LaunchServices re-registration after switchoutput.rs— colored terminal output helpersops/init.rs— bootstrap: installs nix + homebrew, scaffolds or clones nix-darwin config (with mac-app-util for Spotlight), detects existing configs, warns about existing brew packagesops/adopt.rs— safe onboarding: captures all installed brew packages into the config, detects PATH collisions with manually installed binaries, offers to pinops/install.rs— install with auto-resolution, alias-aware duplicate detection, atomic revert on failure, prefer-nix-on-equal preferenceops/identity.rs— StyreneIdentity lifecycle:init(generate encrypted key),show(display hash + pubkeys),link(enroll with Signum hub). Usesstyrene-identitycrate for HKDF derivation, Ed25519 signing, argon2id file encryption. All secrets zeroized after use.ops/profile.rs— apply a machine profile from a GitHub repoops/profile.rs— apply, sign, and verify machine profiles.applyresolves profile chain (extends/compose), merges layers, renders to nix modules.signcanonicalizes merged TOML + source ref, signs with Ed25519 identity, embeds pubkey.verifychecks signature using embedded pubkey (no private key needed).ops/forge.rs— build a bootable NixOS installer USB, optionally with a baked-in profile. Interactive mode prompts for profile, hostname, arch, disk, WiFi, SSH key when flags are omitted.ops/polymerize.rs— interactive NixOS installer (runs on target after booting from USB)ops/build_image.rs— build an OCI container image from a profileops/develop.rs— enter a dev shell from a flake (wrapsnix develop)ops/dev.rs— open a project with omegon AI coding agentops/relocate.rs— move a system-owned config (e.g. /etc/nixos) into a user-writable directoryops/migrate.rs— report: identifies brew packages that could move to nixops/doctor.rs— config health checks: patches in mac-app-util if missingops/self_update.rs— downloads latest release binary from GitHub, replaces self in-placeops/— remaining subcommands: remove, list, search, switch, update, rollback, try, diff, gc
just validate # format-check + lint + test
just test # cargo test
just lint # cargo clippy --all-targets --no-deps -- -D warnings
just format # cargo fmt
just build # debug build
just install # cargo install --path .
just integration # containerized integration tests (docker/podman)- Line-based editing, not a nix parser. Matches on known list patterns (
home.packages = with pkgs; [...];). Works because we control the file format. - Atomic edits. Backup before edit, revert all on failed switch, delete backups on success.
- Smart resolution with aliases.
nex install zedresolveszed-editorin nixpkgs;nex install rgcatches existingripgrep. Versions compared across sources. - Prefer-nix-on-equal. When nix and brew have the same version, defaults to nix. Operator can opt into "always nix" which persists to config.toml and silences future prompts for equal versions. Brew-newer always prompts regardless.
- Safe onboarding.
nex adoptcaptures existing brew state before first switch socleanup = "zap"doesn't nuke packages. PATH collision detection with version comparison and pin option. - Synchronous. No tokio runtime. File I/O and subprocesses are blocking. (tokio exists as a transitive dep via styrene-identity's async trait, but nex never spawns a runtime — it uses the sync
FileSigner::load()API directly.) nex initbootstraps everything. Installs Determinate Nix + Homebrew if missing, scaffolds a minimal nix-darwin config with mac-app-util (or clones with--from), detects existing configs, warns about existing brew packages.- Spotlight integration. Scaffold includes mac-app-util for Finder-alias-based .app bundles. After each switch, re-registers apps with LaunchServices for icon display.
- Nix binary fallback. exec.rs resolves nix from well-known paths (
/nix/var/nix/profiles/default/bin/nix) when it's not yet in PATH (fresh init, same shell).
The edit engine depends on these exact patterns in the target nix-darwin repo:
| List | Open pattern | Item indent | Quoting |
|---|---|---|---|
| nix packages | home.packages = with pkgs; [ |
4 spaces | bare |
| brews | brews = [ |
6 spaces | quoted |
| casks | casks = [ |
6 spaces | quoted |
The scaffold in ops/init.rs generates files that match these patterns exactly.
tests/integration/ contains a containerized test suite (Docker/Podman) with mock binaries for nix, brew, darwin-rebuild, scutil, and sudo. 102 assertions across 10 test suites covering init, list, install, remove, revert, migrate, config resolution, and edge cases. Run via just integration.
site/ contains an Astro project deployed to nex.styrene.io via Cloudflare Pages. The install script at site/public/install.sh is served at https://nex.styrene.io/install.sh. Install script is POSIX sh (no bashisms), installs prebuilt binary to /usr/local/bin (or ~/.local/bin with auto PATH patching), with nix and cargo fallbacks. _headers disables CDN cache on install.sh.
Tag vX.Y.Z triggers .github/workflows/release.yml: builds binaries for 4 targets (aarch64-darwin, x86_64-darwin, aarch64-linux, x86_64-linux), publishes to crates.io, creates GitHub release with tarballs.
nex identity subcommand manages StyreneIdentity — a deterministic key hierarchy where one root secret derives SSH, git signing, age, and agent delegation keys via HKDF-SHA256. Depends on styrene-identity 0.1.1 from crates.io.
nex identity init— generate~/.config/styrene/identity.key(argon2id + ChaCha20Poly1305 encrypted)nex identity show— display identity hash, signing pubkey, SSH host key, age keynex identity link <url>— enroll with a Signum hub (browser or invite code flow)
Identity hash = SHA-256(RNS-signing-Ed25519-pubkey) truncated to 16 bytes (32 hex chars). This is the canonical mesh identity used across Signum, styrened, and cross-service attribution.
Security hardware aliases in aliases.rs ensure nex install ykman (→ yubikey-manager), fido2 (→ libfido2), opensc, pcsc-tools, etc. resolve correctly and cross-detect with brew formula names during adopt.
- macos-nix — the nix-darwin config repo that nex edits
- styrene-rs — canonical home for
styrene-identityand allstyrene-*Rust crates