Contributing
One of the main ideas is to be as modular as possible, so don't create modules that do too many things at the same time, always try to separate them into smaller modules, with unique responsibilities.
Table of Contents
- Rules
- Libs
- Creating a module
- Deprecate a module
- Creating a category
- Hardening a filesystem
- Creating a preset
- Disable or blacklist a kernel module
- Creating a kernel module combo
- Adding functions to the lib
- Licensing and copyright
Rules
- Whenever you need to use lib/builtins functions, use the
lattribute, which is available in all modules. It contains all lib functions, builtins, and additional nix-mineral functions. - Format the code with the formatter in
flake.nix. If using NixOS, use thenix fmtcommand within the repository, which will format all files with the correct formatter. - Explain the options in their descriptions (or in comments). The reason for each setting, and WHY this setting makes the system more secure for the user.
- When creating new settings, use names in lowercase and hyphens to separate words, for example:
example-feature-x. - Invert options that are intended to disable something, for example: don't use
disable-example-feature-y, useexample-feature-yand set the default tofalse. - Use the
l.mkBoolOptionfunction to create boolean options. - Do not attempt to add any feature which has been intentionally excluded under OMITTED.md or ADDITIONAL-RESOURCES.md without first creating an issue.
- In general, do not add stateful features. All changes should be readily able to be rolled back with a rebuild.
- Issues should be created for most feature changes before attempting a PR. Bug fixes, and minor documentation changes do not require issues, but it is still heavily encouraged.
- Where applicable, update both presets and the README to reflect any new options.
- Use
l.mkOverride 900overl.mkForcewhen forcefully overriding NixOS defaults is necessary, to make sure it actually applies. - Where applicable, add GPL license headers to new source code files:
# This file is part of nix-mineral (https://github.com/cynicsketch/nix-mineral/).
# Copyright (c) 2025 cynicsketch
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
Libs
nix-mineral has a library with things used in several files in the project, to avoid code repetition. Common things you'll likely use are explained here. Each function declaration and detailed explanations are located in the lib/default.nix file.
l is an attrset with all the lib functions, builtins functions, and nix-mineral functions. So use l to access any function below.
l.mkBoolOption: Creates a boolean option with description and default value.
Takes 2 arguments: desc, bool
Example:
# Creates an option with mkOption which is a boolean, with the description "Enable example feature X" and default value `true`
l.mkBoolOption "Enable example feature X" true
l.sources: Attrset with all sources inside the lib/sources.toml file (Used with fetchGhFile, fetchGit, fetchTarball, etc).
l.fetchGhFile: Fetches a file directly from a GitHub repository.
Args:
user: GitHub username or organization name.repo: Repository name.rev: Branch, tag or commit.file: Path to the file within the repository.sha256: Hash sha256 of the file.
Examples:
l.fetchGhFile {
user = "cynicsketch";
repo = "nix-mineral";
rev = "b76cc19351f159cd360d8ceea3ba9aff8fce6c6e";
file = "README.md";
sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}
Using with l.sources:
# Fetches the example-file from the repository defined in l.sources.example-file
l.fetchGhFile l.sources.example-file
l.importModule: Import wrapper to pass extra args to a module, and also pass the lib l.
Takes 2 arguments: module, extraArgs
Example:
# Imports the module at ./example-module.nix, passing the lib `l` and an extra argument `customArg` with value "value"
# The imported module is the same as using the nix import function.
l.importModule ./example-module.nix { customArg = "value"; }
# Imports a module with no extra arguments, just passing the lib `l`
l.importModule ({...}: { }) { }
l.mkDeprecatedOption: Creates a deprecated option, forcing the default value to be null. It is intended to be used with l.mkDeprecatedOptionModule to have a deprecation warning if the option is set to a non-null value Read more.
Takes 1 argument: attrs
Example:
{
options = {
# Creates a deprecated option, the default value is converted to null,
# and a deprecation warning is added to the description of the option.
# `attrs` can be any attrset that can be passed to `mkOption`.
my-deprecated-option = l.mkDeprecatedOption {
description = "Example description";
default = true;
example = false;
type = l.types.bool;
};
# `attrs` can also be a string,
# this will create a boolean option with the description set to the string.
my-deprecated-option-2 = l.mkDeprecatedOption "Example description";
};
}
l.mkDeprecatedOptionModule: Returns a module that shows a deprecation warning if the option defined in the optionPath argument is not null. It is intended to be used with l.mkDeprecatedOption to create a deprecated option with the default value set to null Read more.
Takes 2 arguments: optionPath, message
Example:
{
# This function is intended to be used inside the `imports` attribute of a module,
# the same way you would use `mkRemovedOptionModule` in nixpkgs.
imports = [
(l.mkDeprecatedOptionModule [ "nix-mineral" "example" "option" "path" ] ''
This is an additional message to be displayed when the deprecation warning is shown.
It can be used to explain why the option was deprecated, and what the user should do instead.
'')
];
}
Creating a module
-
Choose a module type:
settings: Main settings, which modify things in thedefaultpreset.extras: Extra settings, which modify things only if specified by the user, and may contain opinionated additional software. -
Choose a category: Enter the folder for your chosen module type, and choose a category to place your module in (or create a new one if needed).
-
Create the module file: Let's say I want to create a
settingsmodule in thesystemcategory. Here's what the process would look like:
First, I create a file example-module.nix inside the system folder with this:
{
pkgs,
# The "l" variable includes all the functions from nixpkgs.lib, builtins, and nix-mineral libs.
# This is to avoid having to declare something like `l = lib // builtins // ...` in every module, as was the case before.
l,
# The "cfg" variable contains the option within the category that has the same name as the file.
# In this case, it's the same as setting `cfg = config.nix-mineral.system.example-module`
cfg,
# The parentCfg variable contains the category configuration if needed.
# In this case, it is the same as setting `parentCfg = config.nix-mineral.system`
parentCfg,
...
}:
{
options = {
# I use the mkBoolOption constructor to create a boolean option,
# with a default description and value.
example-module = l.mkBoolOption ''
This is just a example module, enabled by default.
'' true;
};
# `cfg` is equivalent to the value defined in `example-module` within the `options` attribute above,
# so using `mkIf`, all the config below will only be enabled if the module itself is enabled.
config = l.mkIf cfg {
environment.systemPackages = with pkgs; [ firefox ];
};
}
-
Import the module: Inside the
systemfolder, there is a file with the namedefault.nix, which imports all modules within that category. You just need to add the relative file path to thecategoryModuleslist inside thesettings/system/default.nixfile. -
Examples of different modules:
settings/system/multilib.nix: A simple module.settings/kernel/cpu-mitigations.nix: A module that uses an enum instead of a bool.extras/misc/usbguard.nix: A module with several options.
Deprecate a module
-
Choose an existing module to deprecate: This assumes you have read the Creating a module section and understand how to create a module. Other steps assumes you are inside the module you want to deprecate.
-
Create a
l.mkDeprecatedOptionModulefunction: Add animportslist with amkDeprecatedOptionModulecall.
{
imports = [
(l.mkDeprecatedOptionModule [ "nix-mineral" "category" "option-name" ] ''
Please use the new module `new-option-name` instead.
'')
];
}
- Use
l.mkDeprecatedOptionin theoptionsattribute: Convert the option inside the module to a deprecated option, using thel.mkDeprecatedOptionfunction.
{
options = {
# In this example, the option `option-name` was originally created with `l.mkBoolOption`,
# so we wrap it with `l.mkDeprecatedOption` to create a deprecated option,
# but if the option was created with `l.mkOption`, we can just replace it with `l.mkDeprecatedOption` and keep the same attributes.
option-name = l.mkDeprecatedOption (l.mkBoolOption "Example description" true);
};
}
- Change the
configattribute condition: Some changes may be needed in the condition of theconfigattribute, as now the option default value isnull, so you may need to change the condition to check if the option is not null.
Complete example of a deprecated module:
{
pkgs,
l,
cfg,
...
}:
{
imports = [
(l.mkDeprecatedOptionModule [ "nix-mineral" "category" "option-name" ] ''
Please use the new module `new-option-name` instead.
'')
];
options = {
option-name = l.mkDeprecatedOption (l.mkBoolOption "Example description" true);
};
# The condition originally was just `l.mkIf cfg`, but now `cfg` can be null,
# so we change it to `l.mkIf (cfg == true)`.
config = l.mkIf (cfg == true) {
environment.systemPackages = with pkgs; [ firefox ];
};
}
Creating a category
-
Choose a module type:
settings: Main settings, which modify things in thedefaultpreset.extras: Extra settings, which modify things only if specified by the user, and may contain opinionated additional software. -
Create a folder with the category name within the chosen module type: Example:
settings/new-category -
Create a
default.nixfile inside the category folder: Example:settings/new-category/default.nixPlace the following code inside the file, adapting the name, description, and the modules you want to add:
{
options,
config,
pkgs,
lib,
l,
cfg,
...
}:
let
categoryModules =
l.mkCategoryModules cfg
[
# Add here the relative paths to the modules within this category
./example-module.nix
]
{
inherit
options
config
pkgs
lib
;
};
in
{
imports = l.mkCategoryImports categoryModules;
options.new-category = l.mkCategoryOptions categoryModules;
config = l.mkCategoryConfig categoryModules;
}
- Import the category:
Within the
nix-mineral.nixfile, import the category by adding the relative path of the category folder to thesettingsModulesorextrasModuleslist, depending on the type of category you created. Example:settings/new-categorywould be imported within thesettingsModuleslist in thenix-mineral.nixfile.
Hardening a filesystem
To harden a filesystem, you follow the same options a normal user would use, but simply place these options inside the filesystems/normal.nix or filesystems/special.nix modules, depending on the type of filesystem you want to harden.
Examples are within the files.
Creating a preset
- Create a file with the preset name inside the
presetsfolder: Example:presets/example-preset.nixThe code inside the file is the same as what a normal user would use to configure nix-mineral, but each option must be set usingmkPresetormkPresetsfunctions.mkPresetis used to set a single option, whilemkPresetsis used to set multiple options within an attrset. An example of a preset below:
{
mkPresets,
...
}:
{
nix-mineral = mkPresets {
settings = {
kernel = {
lockdown = false;
only-signed-modules = false;
};
};
extras = {
system = {
lock-root = true;
};
};
};
}
- Add your preset to the
presets/default.nixfile: In thepresetsattrset at the top of the file, add your preset with the name as the key and the description as the value. Your preset name must match the filename you created in step 1. This will automatically import the preset file when the user includes the preset name in their configuration.
Disable or blacklist a kernel module
Use nix-mineral.kernel-modules.disable or nix-mineral.kernel-modules.blacklist. Both are attrsets, simply add a kernel module name as a key and the value as a boolean, where true means the module will be disabled or blacklisted, and false means it will not be disabled or blacklisted. Example:
{
nix-mineral.kernel-modules = {
# Disable the kernel module "example-module"
disable.example-module = true;
# Blacklist the kernel module "example-module"
blacklist.example-module = true;
};
}
You can use this to manage kernel modules within modules or presets. If you need to manage multiple kernel modules at once, we encourage you to create a kernel module combo instead of managing them individually.
Creating a kernel module combo
This assumes you have read the Disabling or blacklisting a kernel module section.
Kernel module combos are a way to manage multiple kernel modules at once, by creating a singular option that will affect a list of kernel modules. This is useful for managing kernel modules that are related to each other, or that are commonly used together.
-
Choose the location of the combo: Combos are created within the
kernel-modules/combosfolder, so if you want to create a combo for disabling kernel modules, you would create a combo inside the filekernel-modules/combos/disable.nix, and if you want to create a combo for blacklisting kernel modules, you would create a combo inside the filekernel-modules/combos/blacklist.nix. -
Choose a name for the combo: Using existing kernel module names is discouraged, since the way combos are enabled/disabled is the same as standard kernel modules. So you can use a suffix like
-related. E.g.bluetooth-relatedfor a combo that affects all kernel modules related to bluetooth. -
Create the combo: Inside the file you chose in step 1, create a new attribute with the name of the combo you chose in step 2, and set the value to an attrset with a attribute of
modules, which is a list of kernel module names that will be affected by the combo. Example:
{
X-related = {
modules = [
"x"
"x2"
"x3"
];
};
}
A simple description is automatically generated for the combo ("Disables/blacklists kernel modules related to X"), and the combo will have a default value of false, so it will not be enabled by default.
But the attrset can have all the attributes that can be passed to mkOption, so you can add a description attribute to the attrset to override the default description, and a default attribute to set the default value of the combo. Example:
{
X-related = {
default = true;
description = "Disables/blacklists kernel modules related to X, which are not needed for most users.";
modules = [
"x"
"x2"
"x3"
];
};
}
Adding functions to the lib
Create your function within the let in in the lib/default.nix file, following the pattern of the other functions.
Then place your function within the flake.lib list at the end of the file, and within the list inside the importModule function as well.
Licensing and copyright
Because it is pragmatic to take from existing research, consideration to licensing applies.
This is not legal advice. The information provided is for general informational purposes only and may not reflect the most current legal developments. The author is not an attorney and makes no guarantees regarding the accuracy or completeness of this information. The author disclaims any liability for actions taken or not taken based on the content provided.
- Invidividual, purely functional configuration decisions and purely factual statements are usually not copyrightable due to merger doctrine.
- Informal crediting exists as a courtesy, not as a way to fulfill proper license attribution requirements.
- If in doubt, do not blindly copy. Use proper license attribution where permitted and required.