Contributing to the catalog#
A catalog contribution makes a module available under an lmx: selector in clients that bundle the updated catalog.
For a personal or team module, start with Writing a module and Reusable modules.
You do not need to change this repository to use your own NixOS configuration.
This page covers the catalog's file layout and local checks. The release process is documented on the documentation site.
Add a small module first#
The smallest catalog entry has two files.
For example, a new jq-tools entry would look like this:
modules/
└── jq-tools/
├── default.nix
└── module.toml
Create modules/jq-tools/default.nix:
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.jq ];
}
Create modules/jq-tools/module.toml:
description = "jq for inspecting and transforming JSON."
Here, pkgs.jq uses the consuming VM's Nixpkgs.
This example does not introduce a separate package pin or a version selector.
The directory name becomes the catalog name.
Once bundled into a client, the entry is selected as lmx:jq-tools:
[nixos]
modules = ["lmx:jq-tools"]
Note
Editing a catalog checkout does not change the catalog embedded in an installed client. To try the module before it is bundled, import its directory as a third-party module using the workflow in Reusable modules.
Follow the metadata contract#
The catalog validator checks the following rules:
Item |
Rule |
|---|---|
Directory name |
At most 63 characters; begins with a lowercase letter; lowercase letters, digits and single separating hyphens |
|
Required regular file: the module's default entry point |
|
Required regular file |
|
Required non-empty string; whitespace alone is invalid |
|
Optional list of unique strings; omitted means no version selectors |
Version selector |
At most 63 characters; digits separated by dots, such as |
|
Must name a declared selector when |
Version entry point |
Every selector requires a regular |
Extra metadata fields |
Rejected; the allowed keys are |
Valid names include jq-tools, python and nodejs. Names such as MyModule, my_module, 1tool and my--tool are invalid.
Selectors such as 3.14 are valid; v3.14, latest and 3.14-beta are invalid.
Every immediate entry under modules/ must be a module directory.
Keep shared catalog documentation outside that directory.
Source: catalog validator.
Add version selectors when needed#
The existing versioned modules share this layout:
modules/go/
├── default.nix
├── module.toml
├── module.nix
├── packages.nix
├── releases.nix
└── versions/
├── 1.24.nix
├── 1.25.nix
├── 1.26.nix
└── 1.27.nix
File |
Responsibility |
|---|---|
|
Lists supported selectors and the default |
|
Imports the selector named by metadata |
|
Passes a selector into the shared module |
|
Configures NixOS and installs the selected tools |
|
Loads the selected package source and checks its version |
|
Records source revisions, hashes and package choices |
The last three filenames are a repository convention, not additional metadata requirements. The validator requires the entry points and metadata described above.
For example, Go declares:
description = "Go compiler, gopls language server, and Delve debugger."
versions = ["1.24", "1.25", "1.26", "1.27"]
default = "1.27"
Its default.nix reads that choice:
let
metadata = builtins.fromTOML (builtins.readFile ./module.toml);
in
import (./versions + "/${metadata.default}.nix")
Its versions/1.27.nix supplies the selector:
import ../module.nix "1.27"
The shared module.nix accepts that string first, then the normal NixOS module arguments.
This keeps the NixOS configuration in one place.
Pin a package source#
Versioned entries fetch Nixpkgs by a specific Git revision and content hash.
packages.nix selects a package from that source and asserts its expected version.
Read the existing Go package loader and release map as a complete example.
When adding a selector to that pattern:
Record the Nixpkgs revision and its correct source hash in
releases.nix.Add the selector's package attribute and exact expected package version.
Add
versions/<selector>.nixpointing to the shared module.Add the selector to
module.toml; changedefaultonly if that is part of the update.Update the module's examples and the catalog reference.
Run the checks below, then test the behavior in a VM.
Keep version assertions and hashes consistent with the actual source. Changing only a displayed version string does not select a different package.
For modules that support several toolchains together, review the unqualified command priority and versioned wrappers as well. For a service such as Docker, document that only one selected version is supported.
Run the repository checks#
From the repository root, use the existing Taskfile entry points:
task --yes ci/fmt
task --yes ci/lint
task --yes ci/test
Command |
Checks |
|---|---|
|
Nix formatting through |
|
Nix code through |
|
Catalog structure and NixOS configuration evaluation |
The Taskfile imports the shared Nix tooling at the pinned v0.0.4 revision.
It is the entry point for the repository's validation commands.
ci/test evaluates both aarch64-linux and x86_64-linux:
Case |
Selected modules |
|---|---|
Each default |
One module's |
All defaults |
Every module's |
Each version |
One declared version entry point |
Version with catalog |
All defaults, with one module replaced by a declared version |
Important
These checks evaluate NixOS derivations. They do not build the packages, boot a VM or run the installed tools. They also do not try every combination of several versions of the same module.
For runtime validation, use the module in a test VM. Check the commands or service it adds. If it supports several versions together, select that combination and check both the ordinary and versioned commands.
Sources: Taskfile, evaluation matrix and test VM configuration.