optnix
optnix
is a fast, terminal-based options searcher for Nix module systems.
There are multiple module systems that Nix users use on a daily basis:
- NixOS (the most well-known one)
- Home Manager
nix-darwin
flake-parts
These systems can have difficult-to-navigate documentation, especially for options in external modules.
optnix
solves that problem, and lets users inspect option values if possible;
just like nix repl
in most cases, but prettier.
Installation
Use the provided flake input:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
optnix.url = "github:water-sucks/optnix";
};
outputs = inputs: {
nixosConfigurations.jdoe = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
({pkgs, ...}: {
environment.systemPackages = [
inputs.optnix.packages.${pkgs.system}.optnix
];
})
];
};
};
}
Or import it inside a Nix expression through fetchTarball
:
{pkgs, ...}: let
optnix-url = "https://github.com/water-sucks/optnix/archive/GITREVORBRANCHDEADBEEFDEADBEEF0000.tar.gz";
optnix = (import "${builtins.fetchTarball optnix}").packages.${pkgs.system}.optnix;
in {
environment.systemPackages = [
optnix
# ...
];
}
When will this be in nixpkgs
/home-manager
/nix-darwin
/etc.?
I'm working on it.
Ideally I'll want to get the whole project into nix-community
too, once it
gains some popularity.
Cache
There is a Cachix cache available. Add the following to your Nix configuration to avoid lengthy rebuilds and fetching extra build-time dependencies:
{
nix.settings = {
substituters = [ "https://watersucks.cachix.org" ];
trusted-public-keys = [
"watersucks.cachix.org-1:6gadPC5R8iLWQ3EUtfu3GFrVY7X6I4Fwz/ihW25Jbv8="
];
};
}
Or if using the Cachix CLI outside a NixOS environment:
$ cachix use watersucks
Extras
There are also some packaged Nix modules for easy usage that all function in the same manner, available at the following paths:
nixosModules.optnix
:: for NixOS systemsdarwinModules.optnix
:: fornix-darwin
systemshomeModules.optnix
:: forhome-manager
systems
Alongside these modules is a function mkLib
; this function is the entry point
for the optnix
Nix library for generating lists.
More information on these resources can be found on the
module page, as well as the API Reference
for what functions are available in the optnix
library.
Additionally, some examples configuring optnix
for different module systems
are available on the recipes page.
Usage
The next few sections describe the different optnix
concepts.
But first:
What's a module system, even?
A module system is a Nix library that allows you to configure a set of exposed options. All the systems mentioned above allow you to configure their respective options with your own values.
While this can be a powerful paradigm for modeling any configuration system, these options can be rather hard to discover. Some of these options are found through web interfaces (like https://search.nixos.org), but many options can remain out of sight without reading source code, such as external module options or external module systems.
More information on how module systems work can be found on nix.dev.
How optnix
Works
optnix
works by ingesting option lists that are generated from these
module systems.
An option list is a list of JSON objects; each JSON object describes a single option, with the following values for an example option:
{
"name": "services.nginx.enable",
"description": "Whether to enable Nginx Web Server.",
"type": "boolean",
"default": {
"_type": "literalExpression",
"text": "false"
},
"example": {
"_type": "literalExpression",
"text": "true"
},
"loc": ["services", "nginx", "enable"],
"readOnly": false,
"declarations": [
"/nix/store/path/nixos/modules/services/web-servers/nginx/default.nix"
]
}
Given an options attribute set, a list of these options can be generated using
lib.optionAttrSetToDocList
from nixpkgs
, or by using wrapper functions provided with optnix
as a Nix
library. This will be seen in later examples.
Operation
There are two modes of operation: interactive (the default) and non-interactive.
Interactive mode will display a search UI that allows looking for options using fuzzy search keywords. Selected options in the list can also be evaluated in order to preview their values.
Non-interactive mode requires a valid option name as input, and will display the option and its values (if applicable) without any user interaction. This kind of output is useful when an option name is known, such as for scripting.
optnix
is controlled through its configuration file (or files) that define
"scopes". For more, look at the following pages:
Scopes
A "scope", in the optnix
context, is a combination of a module system option
list, as well as a module system instantiation.
For example, a "scope" for a NixOS configuration located at the flake ref
github:water-sucks/nixed#nixosConfigurations.CharlesWoodson
would be:
- The module system's
options
set (nixosConfigurations.CharlesWoodson.options
) - The module system's
config
set (nixosConfigurations.CharlesWoodson.config
)
options
would be used to generate the option list, while config
would be
used to evaluate and preview values of those options.
Components
In optnix
, scopes are defined from the configuration file, and have a few
components.
An example of the fields for a scope in optnix
can be found on the
configuration page, and real configuration examples can be
found on the recipes page.
scopes.<name>.description
A small description of what the purpose of this scope is. Optional, but useful for command-line completion and listing scopes more descriptively.
scopes.<name>.options-list-{file,cmd}
The option list can be specified in two different ways:
- A path to a JSON file containing the option list (
options-list-file
) - A command that prints the option list to
stdout
(options-list-cmd
)
Specifying at least one of these two is mandatory for every scope.
options-list-file
is preferred over options-list-cmd
, but both can be
specified; if the file does not exist or cannot be accessed/is incorrect, then
the command is used as a fallback.
The command will usually end up being some invocation of Nix, but this command
is evaluated using a shell (/bin/sh
), which means it supports POSIX shell
constructs/available commands as long as they are in $PATH
.
Prefer using options-list-file
when creating configurations, since this is
almost always faster than running the equivalent options-list-cmd
, since
options-list-cmd
is not cached.
Generating options list files can be done using the optnix
Nix library, and
examples can be seen on the recipes page.
scopes.<name>.evaluator
An evaluator is a command template that can be used to evaluate a Nix configuration to retrieve values.
It is a shell command (always some invocation of a Nix command), but with a
twist: exactly one placeholder of {{ .Option }}
for the option to evaluate is
required. This will be filled in with the option to evaluate.
An example evaluator for a Nix flake would be:
nix eval "/path/to/flake#nixosConfigurations.nixos.config.{{ .Option }}"
Specifying an evaluator for a scope is optional.
Configuration
Configurations are defined in TOML
format, and are merged
together in order of priority.
There are four possible locations for configurations (in order of highest to lowest priority, and only if they exist):
- Configuration paths specified on the command line with
--config
optnix.toml
in the current directory$XDG_CONFIG_HOME/optnix/config.toml
or$HOME/.config/optnix/config.toml
/etc/optnix/config.toml
Schema
A more in-depth explanation for scope configuration values is on the scopes page.
# Minimum score required for search matches; the higher the score,
# the more fuzzy matches are required before it is displayed in the list.
# Higher scores will generally lead to less (but more relevant) results, but
# this has diminishing returns.
min_score = 1
# Debounce time for search, in ms
debounce_time = 25
# Default scope to use if not specified on the command line
default_scope = ""
# Formatter command to use for evaluated values, if available. Takes input on
# stdin and outputs the formatted code back to stdout.
formatter_cmd = "nixfmt"
# <name> is a placeholder for the name of the scope.
# This is not a working scope! See the recipes page.
# for real examples.
[scopes.<name>]
# Description of this scope
description = "NixOS configuration for `nixos` system"
# A path to the options list file. Preferred over options-list-cmd.
options-list-file = "/path/to/file"
# A command to run to generate the options list file. The list must be
# printed on stdout.
# Check the recipes page for some example commands that can generate this.
# This is not always needed for every scope.
# The following is only an example.
options-list-cmd = """
nix eval /path/to/flake#homeConfigurations.nixos --json --apply 'input: let
inherit (input) options pkgs;
optionsList = builtins.filter
(v: v.visible && !v.internal)
(pkgs.lib.optionAttrSetToDocList options);
in
optionsList'
"""
# Go template for what to run in order to evaluate the option. Optional, but
# useful for previewing values.
# Check the scopes page for an explanation of this value.
evaluator = "nix eval /path/to/flake#nixosConfigurations.nixos.config.{{ .Option }}"
Module
There are some packaged Nix modules for easy usage that all function in the same manner, available at the following paths (for both flake and legacy-style configs):
nixosModules.optnix
:: for NixOS systemsdarwinModules.optnix
:: fornix-darwin
systemshomeModules.optnix
:: forhome-manager
systems
They all contain the same options.
Library
When using the Nix modules, it is extremely useful to instantiate the Nix
library provided with optnix
.
This can be done using the exported optnix.mkLib
function:
{pkgs, ...}:
let
# Assume `optnix` is imported already.
optnixLib = optnix.mkLib pkgs;
in {
programs.optnix = {
# whatever options
};
}
The functions creates option lists from Nix code ahead of time.
See the API Reference for what functions are available, as
well as the recipes page for some real-life examples on
how to use the module and the corresponding functions from an instantiated
optnix
library.
Options
programs.optnix.enable
CLI searcher for Nix module system options
Type: boolean
Default: false
Example: true
programs.optnix.package
Package that provides optnix
Type: lib.types.package
Default: self.packages.${pkgs.system}.optnix
programs.optnix.settings
Settings to put into optnix.toml
Type: lib.types.attrs
Default: {}
Generated with nix-options-doc
Recipes
There are four popular sets of Nix module systems:
Even despite their popularity, it can be a little hard to get things up and running without knowing how those module systems work first.
The following sections are a set of common scope configurations that you can use
in your configurations, specified in both Nix form using optnix.mkLib
and raw
TOML, whenever necessary.
Make sure to look at the API Reference to check what
arguments can be passed to optnix
library functions.
⚠️ CAUTION: Do not assume that these will automatically work with your setup. Tweak as needed.
Feel free to contribute more examples, or request more for different module systems, as needed.
NixOS Recipes
optnix
recipes for the NixOS module system.
optnix
Module Examples
A simple nix-darwin
module showcasing the programs.optnix
option and using
optnixLib.mkOptionList
for option list generation:
{ options, pkgs, ... }: let
# Assume `optnix` is correctly instantiated.
optnixLib = inputs.optnix.mkLib pkgs;
in {
programs.optnix = {
enable = true;
settings = {
min_score = 3;
scopes = {
CharlesWoodson = {
description = "NixOS configuration for CharlesWoodson";
options-list-file = optnixLib.mkOptionsList { inherit options; };
# For flake systems
# evaluator = "nix eval /path/to/flake#nixosConfigurations.CharlesWoodson.config.{{ .Option }}";
# For legacy systems
# evaluator = "nix-instantiate --eval '<nixpkgs/nixos>' -A config.{{ .Option }}";
};
};
};
};
}
Raw TOML Examples
Flakes
Inside a flake directory /path/to/flake
with a NixOS system named
CharlesWoodson
:
[scopes.nixos]
description = "NixOS flake configuration for CharlesWoodson"
options-list-cmd = '''
nix eval "/path/to/flake#nixosConfigurations.CharlesWoodson" --json --apply 'input: let
inherit (input) options pkgs;
optionsList = builtins.filter
(v: v.visible && !v.internal)
(pkgs.lib.optionAttrSetToDocList options);
in
optionsList'
'''
evaluator = "nix eval /path/to/flake#nixosConfigurations.CharlesWoodson.config.{{ .Option }}"
Legacy
This uses the local NixOS system attributes located in <nixos/nixpkgs>
.
[scopes.nixos-legacy]
description = "NixOS flake configuration on local host"
options-list-cmd = '''
nix-instantiate --eval --expr --strict --json 'let
system = import <nixpkgs/nixos> {};
pkgs = system.pkgs;
optionsList = pkgs.lib.optionAttrSetToDocList system.options;
in
optionsList'
'''
evaluator = "nix-instantiate --eval '<nixpkgs/nixos>' -A config.{{ .Option }}"
nix-darwin
Recipes
optnix
recipes for the nix-darwin
module system.
optnix
Module Examples
A simple nix-darwin
module showcasing the programs.optnix
option and using
optnixLib.mkOptionList
for option list generation:
{ options, pkgs, ...
}: let
# Assume `optnix` is correctly instantiated.
optnixLib = inputs.optnix.mkLib pkgs;
in {
programs.optnix = {
enable = true;
settings = {
min_score = 3;
scopes = {
TimBrown = {
description = "nix-darwin configuration for TimBrown";
options-list-file = optnixLib.mkOptionsList { inherit options; };
# For flake systems
# evaluator = "nix eval /path/to/flake#darwinConfigurations.TimBrown.config.{{ .Option }}";
# For legacy systems
# evaluator = "nix-instantiate --eval '<darwin-config>' -A {{ .Option }}";
};
};
};
};
}
Raw TOML Examples
Flakes
Inside a flake directory /path/to/flake
with a nix-darwin
system named
TimBrown
:
[scopes.nix-darwin]
description = "nix-darwin flake configuration for TimBrown"
options-list-cmd = '''
nix eval "/path/to/flake#darwinConfigurations.TimBrown" --json --apply 'input: let
inherit (input) options pkgs;
optionsList = builtins.filter
(v: v.visible && !v.internal)
(pkgs.lib.optionAttrSetToDocList options);
in
optionsList'
'''
evaluator = "nix eval /path/to/flake#darwinConfigurations.TimBrown.config.{{ .Option }}"
Legacy
TODO: add an example of legacy, non-flake nix-darwin configuration with raw TOML.
home-manager
Recipes
optnix
recipes for
home-manager
(HM), a popular
module system that manages user configurations.
HM has some quirks that can make it rather weird to use in optnix
. Absolutely
prefer using the optnix
modules + the optnix
Nix library to generate HM
options. If using optnix
with HM with raw TOML, you are on your own; I have
not been able to create good examples at the time of writing.
Standalone/Inside home-manager
Module
Inside of home-manager
, the options
attribute is available and can be used
directly.
{ options, config, pkgs, lib, ... }: let
# Assume `optnix` is correctly instantiated.
optnixLib = optnix.mkLib pkgs;
in {
programs.optnix = {
enable = true;
scopes = {
home-manager = {
description = "home-manager configuration for all systems";
options-list-file = optnixLib.mkOptionsList {
inherit options;
transform = o:
o
// {
name = lib.removePrefix "home-manager.users.${config.home.username}." o.name;
};
};
evaluator = "";
};
}
};
}
NOTE: This may create a separate configuration file for ALL users depending on, so it may not necessarily be what you want on multi-user systems. Look at the next section for more on an alternative.
NixOS/nix-darwin
Module
home-manager
does not expose a proper options
attribute set on NixOS and
nix-darwin
systems, which makes option introspection a little harder than it
should be.
Instead, an unexposed function from the type of the home-manager.users
option
itself, getSubOptions
, can be used to obtain an options
attribute set for
HM.
However, this is impossible to evaluate due to the fact that it relies on other settings that may not exist, such as usernames and such.
When this is the case, the following scope declaration using the special
function optnixLib.mkOptionsListFromHMSource
can be used in any module: NixOS,
nix-darwin
, or even in standalone HM.
This function is adapted from home-manager
's documentation facilities, and
inserts some dummy modules that allow for proper option list generation without
evaluation failing.
{
inputs,
config,
pkgs,
...
}: let
optnixLib = inputs.optnix.mkLib pkgs;
in {
programs.optnix = {
enable = true;
settings = {
scopes = {
home-manager = {
description = "home-manager options for all systems";
options-list-file = optnixLib.hm.mkOptionsListFromHMSource {
home-manager = inputs.home;
modules = with inputs; [
# Other extra modules that may exist in your source tree
# optnix.homeModules.optnix
];
};
};
};
};
};
}
flake-parts
Recipes
flake-parts
is a framework/module system for writing
flakes using Nix module system semantics.
Singe flake-parts
configurations can wildly vary in module selection, most
users will want to define them on a per-flake basis. This is well-supported
through the usage of a local optnix.toml
file, relative to the flake.
Exposing Documentation Through Flake
Use the following flake module code to expose a flake attribute called
debug.options-doc
:
{
lib,
options,
...
}: {
# Required for evaluating module option values.
debug = true;
flake = {
debug.options-doc = builtins.unsafeDiscardStringContext
(builtins.toJSON (lib.optionAttrSetToDocList options));
};
}
OR, if you do not want to copy-paste code, use the
optnix.flakeModules.flake-parts-doc
in an import like so:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
optnix.url = "github:water-sucks/optnix";
};
outputs = { optnix, ... }@inputs:
flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
optnix.flakeModules.flake-parts-doc
];
# ...
};
}
Then, configure an optnix.toml
(in the same directory as the top-level
flake.nix
) for this is rather trivial:
[scopes.flake-parts]
description = "flake-parts config for NixOS configuration"
options-list-cmd = "nix eval --json .#debug.options-doc"
evaluator = "nix eval .#debug.config.{{ .Option }}"
Despite the usage of options-list-cmd
, flake-parts
evaluates decently fast
at most times.
If using options-list-file
is a non-negotiable, exposing a package with
pkgs.writeText
and using the above code as a base is also possible. But you're
on your own. If you really want an example, congrats on reading this! I love
that you're taking the time to read through this application's documentation and
trying to use it, so please file an issue, I was just too lazy to do this right
now at time of writing.
API Reference
mkLib
mkLib :: pkgs -> set
Creates an optnixLib
instance using an instantiated pkgs
set.
optnixLib
optnixLib
represents an instantiated value crated by mkLib
above.
optnixLib.mkOptionsList
mkOptionsList :: AttrSet -> Derivation
Generates an options.json
derivation that contains a JSON options list from a
provided options attribute set that can be linked into various Nix module
systems.
A base (but common pattern) of usage:
{options, pkgs, ...}: let
# Adapt this to wherever `optnix` came from
optnixLib = optnix.mkLib pkgs;
optionsList = mkOptionsList {
inherit options;
excluded = []; # Add problematic eval'd paths here
};
in {
# use here...
}
Arguments are passed as a single attribute set in a named manner.
Required Attributes
options :: AttrSet
:: The options attrset to generate an option list for
Optional Attributes
transform :: AttrSet -> AttrSet
:: A function to apply to each generated option in the list, useful for stripping prefixes, whereAttrSet
is a single option in the listexcluded :: [String]
:: A list of dot-paths in the options attribute set to exclude from the generated options list usingoptnixLib.removeNestedAttrs
optnixLib.mkOptionsListFromModules
mkOptionsListFromModules :: AttrSet -> Derivation
Generates an options.json
derivation that contains a JSON options list from a
provided list of modules that can be linked into various Nix module systems.
This can be useful when generating documentation for external modules that are
not necessarily part of the configuration, such as generating option lists for
usage outside of optnix
, or for generating a system-agnostic documentation
list.
Arguments are passed as a single attribute set in a named manner.
Arguments
modules :: List[Module]
:: A list of modules to generate an option list for
optnixLib.combineLists
combineLists :: [Derivation] -> Derivation
Combines together multiple JSON file derivations containing option lists (such
as those created by mkOptionsList
) into a single JSON file.
combineLists [optionsList1 optionsList2]
Internally uses jq --slurp
add to merge JSON arrays.
Arguments
[Derivation]
:: A list of JSON file derivations, each containing an option list, preferably created usingmkOptionsList
optnixLib.removeAtPath
removeAtPath :: String -> AttrSet -> AttrSet
Recursively remove a nested attribute from an attrset, following a string array containing the path to remove.
This can be useful for removing problematic options (i.e. ones that fail
evaluation) in a custom manner, and is also used internally by the excluded
parameter of mkOptionsList
.
removeAtPath "services.nginx" options
This example will return the same attrset config, but with the services.nginx
subtree removed. If a path does not exist or is not an attrset, it is left
untouched.
Arguments
String
:: A dot-path string representing the attribute path to remove from the attrsetAttrSet
:: The attrset to remove attributes recursively from
optnixLib.removeNestedAttrs
removeNestedAttrs :: [String] -> AttrSet -> AttrSet
Recursively remove multiple attributes using optnixLib.removeAtPath
.
removeNestedAttrs ["programs.chromium" "services.emacs"] options
This example will return the same attrset config, but with the services.emacs
and programs.chromium
subtrees removed. If a path does not exist or is not an
attrset, it is left untouched.
Arguments
[String]
:: A list of dot-path strings representing the attribute paths to remove from the attrsetAttrSet
:: The attrset to remove attributes recursively from
optnixLib.hm
Functions for optnixLib
specifically related to
home-manager
.
optnixLib.hm.mkOptionsListFromHMSource
mkOptionsListFromHMSource :: AttrSet -> Derivation
Generate a JSON options list given a home-manager
source tree, alongside
additional home-manager
modules if desired.
This implementation is adapted from Home Manager's internal documentation
generation pipeline and simplified for optnix
usage. Prefer using
mkOptionsList
with an explicit instantiated options
attribute set if
possible.
Required Attributes
home-manager :: Derivation
:: The derivation containinghome-manager
, usually sourced usingfetchTarball
, a Nix channel, or a flake input
Optional Attributes
modules :: List[Module]
:: A list of extra modules to additionally evaluate when generating the option list