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

  • Whenever you need to use lib/builtins functions, use the l attribute, 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 the nix fmt command 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, use example-feature-y and set the default to false.
  • Use the l.mkBoolOption function 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 900 over l.mkForce when 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

  1. Choose a module type: settings: Main settings, which modify things in the default preset. extras: Extra settings, which modify things only if specified by the user, and may contain opinionated additional software.

  2. 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).

  3. Create the module file: Let's say I want to create a settings module in the system category. 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 ];
  };
}
  1. Import the module: Inside the system folder, there is a file with the name default.nix, which imports all modules within that category. You just need to add the relative file path to the categoryModules list inside the settings/system/default.nix file.

  2. 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

  1. 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.

  2. Create a l.mkDeprecatedOptionModule function: Add an imports list with a mkDeprecatedOptionModule call.

{
  imports = [
    (l.mkDeprecatedOptionModule [ "nix-mineral" "category" "option-name" ] ''
      Please use the new module `new-option-name` instead.
    '')
  ];
}
  1. Use l.mkDeprecatedOption in the options attribute: Convert the option inside the module to a deprecated option, using the l.mkDeprecatedOption function.
{
  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);
  };
}
  1. Change the config attribute condition: Some changes may be needed in the condition of the config attribute, as now the option default value is null, 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

  1. Choose a module type: settings: Main settings, which modify things in the default preset. extras: Extra settings, which modify things only if specified by the user, and may contain opinionated additional software.

  2. Create a folder with the category name within the chosen module type: Example: settings/new-category

  3. Create a default.nix file inside the category folder: Example: settings/new-category/default.nix Place 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;
}
  1. Import the category: Within the nix-mineral.nix file, import the category by adding the relative path of the category folder to the settingsModules or extrasModules list, depending on the type of category you created. Example: settings/new-category would be imported within the settingsModules list in the nix-mineral.nix file.

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

  1. Create a file with the preset name inside the presets folder: Example: presets/example-preset.nix The code inside the file is the same as what a normal user would use to configure nix-mineral, but each option must be set using mkPreset or mkPresets functions. mkPreset is used to set a single option, while mkPresets is 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;
      };
    };
  };
}
  1. Add your preset to the presets/default.nix file: In the presets attrset 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.

  1. Choose the location of the combo: Combos are created within the kernel-modules/combos folder, so if you want to create a combo for disabling kernel modules, you would create a combo inside the file kernel-modules/combos/disable.nix, and if you want to create a combo for blacklisting kernel modules, you would create a combo inside the file kernel-modules/combos/blacklist.nix.

  2. 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-related for a combo that affects all kernel modules related to bluetooth.

  3. 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.