diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 4153848..a0e70c0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -252,8 +252,8 @@ function themeConfigEnglish() { link: "/other-usage-of-flakes/the-new-cli.md", }, { - text: "[WIP]Nix Options", - link: "/other-usage-of-flakes/options.md", + text: "Module System & Custom Options", + link: "/other-usage-of-flakes/module-system.md", }, { text: "[WIP]Testing", @@ -442,8 +442,8 @@ function themeConfigChinese() { link: "/zh/other-usage-of-flakes/the-new-cli.md", }, { - text: "[WIP]Nix Options", - link: "/zh/other-usage-of-flakes/options.md", + text: "模块系统与自定义 options", + link: "/zh/other-usage-of-flakes/module-system.md", }, { text: "[WIP]Testing", diff --git a/docs/introduction/advantages-and-disadvantages.md b/docs/introduction/advantages-and-disadvantages.md index eb923cd..86fb4ec 100644 --- a/docs/introduction/advantages-and-disadvantages.md +++ b/docs/introduction/advantages-and-disadvantages.md @@ -33,7 +33,7 @@ - **High Learning Curve**: - Achieving complete reproducibility and avoiding pitfalls associated with improper usage requires learning about Nix's entire design and managing the system declaratively, rather than blindly using commands like `nix-env -i` (similar to `apt-get install`). - **Disorganized Documentation**: - - Currently, Nix Flakes remains an experimental feature, and there is limited documentation specifically focused on it. Most Nix community documentation primarily covers the older `nix-env`/`nix-channel` approach. If you want to start learning directly from Nix Flakes, you need to refer to a significant amount of outdated documentation and extract the relevant information. Additionally, some core features of Nix, such as `imports` and the Nix Module System, lack detailed official documentation, requiring resorting to source code analysis. + - Currently, Nix Flakes remains an experimental feature, and there is limited documentation specifically focused on it. Most Nix community documentation primarily covers the older `nix-env`/`nix-channel` approach. If you want to start learning directly from Nix Flakes, you need to refer to a significant amount of outdated documentation and extract the relevant information. Additionally, some core features of Nix, such as `imports` and the Nixpkgs Module System, lack detailed official documentation, requiring resorting to source code analysis. - **Increased Disk Space Usage**: - To ensure the ability to roll back the system at any time, Nix retains all historical environments by default, resulting in increased disk space usage. - While this additional space usage may not be a concern on desktop computers, it can become problematic on resource-constrained cloud servers. diff --git a/docs/nixos-with-flakes/modularize-the-configuration.md b/docs/nixos-with-flakes/modularize-the-configuration.md index ce6dbfa..b7d9ea1 100644 --- a/docs/nixos-with-flakes/modularize-the-configuration.md +++ b/docs/nixos-with-flakes/modularize-the-configuration.md @@ -274,6 +274,8 @@ As you can see, the order of `systemPackages` is `git -> curl -> default package > Although adjusting the order of `systemPackages` may not be useful in practice, it can be helpful in other scenarios. +> For a deeper introduction to the module system, see [Module System & Custom Options](../other-usage-of-flakes/module-system.md). + ## References - [Nix modules: Improving Nix's discoverability and usability](https://cfp.nixcon.org/nixcon2020/talk/K89WJY/) diff --git a/docs/nixos-with-flakes/nixos-with-flakes-enabled.md b/docs/nixos-with-flakes/nixos-with-flakes-enabled.md index 9fe84a0..62d59eb 100644 --- a/docs/nixos-with-flakes/nixos-with-flakes-enabled.md +++ b/docs/nixos-with-flakes/nixos-with-flakes-enabled.md @@ -116,15 +116,15 @@ Note that the copied template cannot be used directly. You need to modify it to # The Nix module system can modularize configuration, # improving the maintainability of configuration. # - # Each parameter in the `modules` is a Nix Module, and + # Each parameter in the `modules` is a Nixpkgs Module, and # there is a partial introduction to it in the nixpkgs manual: # # It is said to be partial because the documentation is not # complete, only some simple introductions. # such is the current state of Nix documentation... # - # A Nix Module can be an attribute set, or a function that - # returns an attribute set. By default, if a Nix Module is a + # A Nixpkgs Module can be an attribute set, or a function that + # returns an attribute set. By default, if a Nixpkgs Module is a # function, this function have the following default parameters: # # lib: the nixpkgs function library, which provides many @@ -155,7 +155,7 @@ Note that the copied template cannot be used directly. You need to modify it to modules = [ # Import the configuration.nix here, so that the # old configuration file can still take effect. - # Note: configuration.nix itself is also a Nix Module, + # Note: configuration.nix itself is also a Nixpkgs Module, ./configuration.nix ]; }; diff --git a/docs/other-usage-of-flakes/module-system.md b/docs/other-usage-of-flakes/module-system.md new file mode 100644 index 0000000..8f51f96 --- /dev/null +++ b/docs/other-usage-of-flakes/module-system.md @@ -0,0 +1,259 @@ +# Module System and Custom Options + +In our previous NixOS configurations, we set various values for `options` to configure NixOS or Home Manager. These `options` are actually defined in two locations: + +> If you are still using nix-darwin, its configuration is similar, and its module system is implemented in [nix-darwin/modules](https://github.com/LnL7/nix-darwin/tree/master/modules). + +- NixOS: [nixpkgs/nixos/modules](https://github.com/NixOS/nixpkgs/tree/23.11/nixos/modules), where all NixOS options visible on are defined. +- Home Manager: [home-manager/modules](https://github.com/nix-community/home-manager/blob/release-23.11/modules), where you can find all its options at . + +The foundation of the aforementioned NixOS Modules and Home Manager Modules is a universal module system implemented in Nixpkgs, found in [lib/modules.nix](lib/modules.nix). The official documentation for this module system is provided below (even for experienced NixOS users, understanding this can be a challenging task): + +- [Module System - Nixpkgs](Module System - Nixpkgs) + +Because the documentation for Nixpkgs' module system is lacking, it directly recommends reading another writing guide specifically for NixOS module system, which is clearer but might still be challenging for newcomers: + +- [Writing NixOS Modules - Nixpkgs](Writing NixOS Modules - Nixpkgs) + +In summary, the module system is implemented by Nixpkgs and is not part of the Nix package manager. Therefore, its documentation is not included in the Nix package manager's documentation. Additionally, both NixOS and Home Manager are based on Nixpkgs' module system implementation. + +## What is the Purpose of the Module System? + +As ordinary users, using various options implemented by NixOS and Home Manager based on the module system is sufficient to meet most of our needs. So, what are the benefits of delving into the module system for us? + +In the earlier discussion on modular configuration, the core idea was to split the configuration into multiple modules and then import these modules using `imports = [ ... ];`. This is the most basic usage of the module system. However, using only `imports = [ ... ];` allows us to import configurations defined in the module as they are without any customization, which limits flexibility. In simple configurations, this method is sufficient, but if the configuration is more complex, it becomes inadequate. + +To illustrate the drawback, let's consider an example. Suppose I manage four NixOS hosts, A, B, C, and D. I want to achieve the following goals while minimizing configuration repetition: + +- All hosts (A, B, C, and D) need to enable the Docker service and set it to start at boot. +- Host A should change the Docker storage driver to `btrfs` while keeping other settings the same. +- Hosts B and C, located in China, need to set a domestic mirror in Docker configuration. +- Host C, located in the United States, has no special requirements. +- Host D, a desktop machine, needs to set an HTTP proxy to accelerate Docker downloads. + +If we purely use `imports`, we might have to split the configuration into several modules like this and then import different modules for each host: + +```bash +› tree +. +├── docker-default.nix # Basic Docker configuration, including starting at boot +├── docker-btrfs.nix # Imports docker-default.nix and changes the storage driver to btrfs +├── docker-china.nix # Imports docker-default.nix and sets a domestic mirror +└── docker-proxy.nix # Imports docker-default.nix and sets an HTTP proxy +``` + +Doesn't this configuration seem redundant? This is still a simple example; if we have more machines with greater configuration differences, the redundancy becomes even more apparent. + +Clearly, we need other means to address this redundant configuration issue, and customizing some of our own `options` is an excellent choice. + +Before delving into the study of the module system, I emphasize once again that the following content is not necessary to learn and use. Many NixOS users have not customized any `options` and are satisfied with simply using `imports` to meet their needs. If you are a newcomer, consider learning this part when you encounter problems that `imports` cannot solve. That's completely okay. + +## Basic Structure and Usage + +The basic structure of modules defined in Nixpkgs is as follows: + +```nix +{ config, pkgs, ... }: + +{ + imports = + [ # import other modules here + ]; + + options = { + # ... + }; + + config = { + # ... + }; +} +``` + +Among these, we are already familiar with `imports = [ ... ];`, but the other two parts are yet to be explored. Let's have a brief introduction here: + +- `options = { ... };`: Similar to variable declarations in programming languages, it is used to declare configurable options. +- `config = { ... };`: Similar to variable assignments in programming languages, it is used to assign values to the options declared in `options`. + +The most typical usage is to, within the same Nixpkgs module, set values for other `options` in `config = { .. };` based on the current values declared in `options = { ... };`. This achieves the functionality of parameterized configuration. + +It's easier to understand with a direct example: + +```nix +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.foo; +in { + options.programs.foo = { + enable = mkEnableOption "the foo program"; + + package = mkOption { + type = types.package; + default = pkgs.jq; + defaultText = literalExpression "pkgs.foo"; + description = "foo package to use."; + }; + + extraConfig = mkOption { + default = ""; + example = '' + foo bar + ''; + type = types.lines; + description = '' + Extra settings for foo. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + xdg.configFile."foo/foorc" = mkIf (cfg.extraConfig != "") { + text = '' + # Generated by Home Manager. + + ${cfg.extraConfig} + ''; + }; + }; +} +``` + +The module defined above introduces three `options`: + +- `programs.foo.enable`: Used to control whether to enable this module. +- `programs.foo.package`: Allows customization of the `foo` package, such as using different versions, setting different compilation parameters, and so on. +- `programs.foo.extraConfig`: Used for customizing the configuration file of `foo`. + +Then, in the `config` section, based on the values declared in these three variables in `options`, different settings are applied: + +- If `programs.foo.enable` is `false` or undefined, no settings are applied. + - This is achieved using `lib.mkIf`. +- Otherwise, + - Add `programs.foo.package` to `home.packages` to install it in the user environment. + - Write the value of `programs.foo.extraConfig` to `~/.config/foo/foorc`. + +This way, we can import this module in another Nix file and achieve custom configuration for `foo` by setting the `options` defined here. For example: + +```nix +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./foo.nix + ]; + + programs.foo ={ + enable = true; + package = pkgs.foo; + extraConfig = '' + foo baz + ''; + }; +} +``` + +In the example above, the way we assign values to `options` is actually a kind of **abbreviation**. When a module declares only `options` without `config` (and other special parameters of the module system), we can omit the `config` prefix and directly use the name of `options` for assignment. + +## Assignment and Lazy Evaluation in the Module System + +The module system takes full advantage of Nix's lazy evaluation feature, which is crucial for achieving parameterized configuration. + +Let's start with a simple example: + +```nix +{ + description = "NixOS Flake for Test"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + home-manager = { + url = "github:nix-community/home-manager/release-23.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = {nixpkgs, ...}: { + nixosConfigurations = { + "test" = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ({ + config, + lib, + ... + }: { + options = { + foo = lib.mkOption { + default = false; + type = lib.types.bool; + }; + }; + + # Scenario 1 (works fine) + config.warnings = if config.foo then ["foo"] else []; + + # Scenario 2 (error: infinite recursion encountered) + # config = if config.foo then { warnings = ["foo"];} else {}; + + # Scenario 3 (works fine) + # config = lib.mkIf config.foo {warnings = ["foo"];}; + }) + ]; + }; + }; + }; +} +``` + +In the examples 1, 2, and 3 of the above configuration, the value of `config.warnings` depends on the value of `config.foo`, but their implementation methods are different. Save the above configuration as `flake.nix`, and then use the command `nix eval .#nixosConfigurations.test.config.warnings` to test examples 1, 2, and 3 separately. You will find that examples 1 and 3 work correctly, while example 2 results in an error: `error: infinite recursion encountered`. + +Let's explain each case: + +1. Example 1 evaluation flow: `config.warnings` => `config.foo` => `config` + 1. First, Nix attempts to compute the value of `config.warnings` but finds that it depends on `config.foo`. + 2. Next, Nix tries to compute the value of `config.foo`, which depends on its outer `config`. + 3. Nix attempts to compute the value of `config`, and since the contents not genuinely used by `config.foo` are lazily evaluated by Nix, there is no recursive dependency on `config.warnings` at this point. + 4. The evaluation of `config.foo` is completed, followed by the assignment of `config.warnings`, and the computation ends. + +2. Example 2: `config` => `config.foo` => `config` + 1. Initially, Nix tries to compute the value of `config` but finds that it depends on `config.foo`. + 2. Next, Nix attempts to compute the value of `config.foo`, which depends on its outer `config`. + 3. Nix tries to compute the value of `config`, and this loops back to step 1, leading to an infinite recursion and eventually an error. + +3. Example 3: The only difference from example 2 is the use of `lib.mkIf` to address the infinite recursion issue. + +The key lies in the function `lib.mkIf`. When using `lib.mkIf` to define `config`, it will be lazily evaluated by Nix. This means that the calculation of `config = lib.mkIf ...` will only occur after the evaluation of `config.foo` is completed. + +The Nixpkgs module system provides a series of functions similar to `lib.mkIf` for parameterized configuration and intelligent module merging: + +1. `lib.mkIf`: Already introduced. +2. `lib.mkOverride` / `lib.mkDefault` / `lib.mkForce`: Previously discussed in [Modularizing NixOS Configuration](../nixos-with-flakes/modularize-the-configuration.md). +3. `lib.mkOrder`, `lib.mkBefore`, and `lib.mkAfter`: As mentioned above. +4. Check [Option Definitions - NixOS][Option Definitions - NixOS] for more functions related to option assignment (definition). + +## Option Declaration and Type Checking + +While assignment is the most commonly used feature of the module system, if you need to customize some `options`, you also need to delve into option declaration and type checking. I find this part relatively straightforward; it's much simpler than assignment, and you can understand the basics by directly referring to the official documentation. I won't go into detail here. + +- [Option Declarations - NixOS][Option Declarations - NixOS] +- [Options Types - NixOS][Options Types - NixOS] + +## References + +- [Best resources for learning about the NixOS module system? - Discourse](https://discourse.nixos.org/t/best-resources-for-learning-about-the-nixos-module-system/1177/4) +- [NixOS modules - NixOS Wiki](https://nixos.wiki/wiki/NixOS_modules) +- [NixOS: config argument - NixOS Wiki](https://nixos.wiki/wiki/NixOS:config_argument) +- [Module System - Nixpkgs][Module System - Nixpkgs] +- [Writing NixOS Modules - Nixpkgs][Writing NixOS Modules - Nixpkgs] + + +[lib/modules.nix]: https://github.com/NixOS/nixpkgs/blob/23.11/lib/modules.nix#L995 +[Module System - Nixpkgs]: https://github.com/NixOS/nixpkgs/blob/23.11/doc/module-system/module-system.chapter.md +[Writing NixOS Modules - Nixpkgs]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/writing-modules.chapter.md +[Option Definitions - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-def.section.md +[Option Declarations - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-declarations.section.md +[Options Types - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-types.section.md diff --git a/docs/other-usage-of-flakes/options.md b/docs/other-usage-of-flakes/options.md deleted file mode 100644 index fe5400a..0000000 --- a/docs/other-usage-of-flakes/options.md +++ /dev/null @@ -1,8 +0,0 @@ -# Nix option definitions - -TODO - -## References - -- [NixOS option definitions - NixOS in Production](https://github.com/Gabriella439/nixos-in-production/blob/ffa037c/manuscript/Modules.md) -- [Advanced option definitions - NixOS in Production](https://github.com/Gabriella439/nixos-in-production/blob/ffa037ce7584bd1138d0f16df69821a8eef42b44/manuscript/AdvancedModules.md) diff --git a/docs/zh/introduction/advantages-and-disadvantages.md b/docs/zh/introduction/advantages-and-disadvantages.md index 8572273..8623252 100644 --- a/docs/zh/introduction/advantages-and-disadvantages.md +++ b/docs/zh/introduction/advantages-and-disadvantages.md @@ -32,7 +32,7 @@ - **学习成本高**: - 如果你希望系统完全可复现,并且避免各种不当使用导致的坑,那就需要学习了解 Nix 的整个设计,并以声明式的方式管理系统,不能无脑 `nix-env -i`(这类似 `apt-get install`)。 - **文档混乱**: - - 首先 Nix Flakes 目前仍然是实验性特性,介绍它本身的文档目前比较匮乏, Nix 社区绝大多数文档都只介绍了旧的 `nix-env`/`nix-channel`,想直接从 Nix Flakes 开始学习的话,需要参考大量旧文档,从中提取出自己需要的内容。另外一些 Nix 当前的核心功能,官方文档都语焉不详(比如 `imports` 跟 Nix Module System),想搞明白基本只能看源码了... + - 首先 Nix Flakes 目前仍然是实验性特性,介绍它本身的文档目前比较匮乏, Nix 社区绝大多数文档都只介绍了旧的 `nix-env`/`nix-channel`,想直接从 Nix Flakes 开始学习的话,需要参考大量旧文档,从中提取出自己需要的内容。另外一些 Nix 当前的核心功能,官方文档都语焉不详(比如 `imports` 跟 Nixpkgs Module System),想搞明白基本只能看源码了... - **比较吃硬盘空间**: - 为了保证系统可以随时回退,nix 默认总是保留所有历史环境,这会使用更多的硬盘空间。 - 多使用的这这些空间,在桌面电脑上可能不是啥事,但是在资源受限的云服务器上可能会是个问题。 diff --git a/docs/zh/nixos-with-flakes/modularize-the-configuration.md b/docs/zh/nixos-with-flakes/modularize-the-configuration.md index 5223fdb..a626dfd 100644 --- a/docs/zh/nixos-with-flakes/modularize-the-configuration.md +++ b/docs/zh/nixos-with-flakes/modularize-the-configuration.md @@ -251,6 +251,8 @@ nix-repl> outputs.nixosConfigurations.nixos-test.config.environment.systemPackag > 虽然单纯调整 `systemPackages` 的顺序没什么用,但是在其他地方可能会有用... +> 对模块系统更深入的介绍,参见 [模块系统与自定义 options](../other-usage-of-flakes/module-system.md). + ## References - [Nix modules: Improving Nix's discoverability and usability ](https://cfp.nixcon.org/nixcon2020/talk/K89WJY/) diff --git a/docs/zh/nixos-with-flakes/nixos-with-flakes-enabled.md b/docs/zh/nixos-with-flakes/nixos-with-flakes-enabled.md index f3c2e3e..e5f301a 100644 --- a/docs/zh/nixos-with-flakes/nixos-with-flakes-enabled.md +++ b/docs/zh/nixos-with-flakes/nixos-with-flakes-enabled.md @@ -110,17 +110,17 @@ cat flake.nix # Nix 模块系统可将配置模块化,提升配置的可维护性 # - # modules 中每个参数,都是一个 Nix Module + # modules 中每个参数,都是一个 Nixpkgs Module # nixpkgs manual 中有半份介绍它的文档: # # 说半份是因为它的文档不全,只有一些简单的介绍(Nix 文档现状...) # - # Nix Module 可以是一个 attribute set, + # Nixpkgs Module 可以是一个 attribute set, # 也可以是一个返回 attribute set 的函数,如果是函数, # 那么它的参数就是当前的 NixOS Module 的参数. # # 根据 Nix Wiki 对 Nix modules 的描述,默认情况下, - # Nix Module 函数的默认参数有几个: + # Nixpkgs Module 函数的默认参数有几个: # # lib: nixpkgs 自带的函数库,提供了许多操作 Nix 表达式的实用函数 # 详见 https://nixos.org/manual/nixpkgs/stable/#id-1.4 @@ -140,7 +140,7 @@ cat flake.nix # specialArgs = {...}; # 将 inputs 中的参数传入所有子模块 modules = [ # 这里导入之前我们使用的 configuration.nix,这样旧的配置文件仍然能生效 - # 注: configuration.nix 本身也是一个 Nix Module,因此可以直接在这里导入 + # 注: configuration.nix 本身也是一个 Nixpkgs Module,因此可以直接在这里导入 ./configuration.nix ]; }; diff --git a/docs/zh/nixpkgs/callpackage.md b/docs/zh/nixpkgs/callpackage.md index 593d21c..d9caa44 100644 --- a/docs/zh/nixpkgs/callpackage.md +++ b/docs/zh/nixpkgs/callpackage.md @@ -131,7 +131,7 @@ nix-repl> import ./hello.nix pkgs }) ``` -那么就可以在任意 Nix Module 中使用 `pkgs.callPackage ./hello.nix {}` 来导入并使用它,并且替换它的任意参数。 +那么就可以在任意 Nixpkgs Module 中使用 `pkgs.callPackage ./hello.nix {}` 来导入并使用它,并且替换它的任意参数。 ```nix { lib, pkgs, pkgsKernel, kernel-src, ... }: diff --git a/docs/zh/other-usage-of-flakes/module-system.md b/docs/zh/other-usage-of-flakes/module-system.md new file mode 100644 index 0000000..9b25001 --- /dev/null +++ b/docs/zh/other-usage-of-flakes/module-system.md @@ -0,0 +1,269 @@ +# 模块系统与自定义 options + +我们在前面的 NixOS 配置中通过设置各种 `options` 的值来配置 NixOS 或者 Home Manager,这些 `options` 实际都在这两个位置定义: + +> 如果你还使用 nix-darwin,那么它的配置也是类似的,其模块系统的实现位于 [nix-darwin/modules](https://github.com/LnL7/nix-darwin/tree/master/modules) + +- NixOS: [nixpkgs/nixos/modules](https://github.com/NixOS/nixpkgs/tree/23.11/nixos/modules), 我们在 中能看到的所有 NixOS options 都是在这里定义的。 +- Home Manager: [home-manager/modules](https://github.com/nix-community/home-manager/blob/release-23.11/modules): 可在 中找到其所有的 options. + +而上述 NixOS Modules 跟 Home Manager Modules 的基础,是 Nixpkgs 中实现的一套通用模块系统 [lib/modules.nix][lib/modules.nix],这套模块系统的官方文档如下(即使是对熟练使用 NixOS 的用户而言,要看懂这玩意儿也不是件容易的事...): + +- [Module System - Nixpkgs][Module System - Nixpkgs] + +因为 Nixpkgs 的模块系统文档没人写,文档中直接建议读另一份专门针对 NixOS 模块系统的编写指南,确实写得清晰一些,但也很难说它对新手有多友好: + +- [Writing NixOS Modules - Nixpkgs][Writing NixOS Modules - Nixpkgs] + +总之,模块系统是由 Nixpkgs 实现的,并不是 Nix 包管理器的一部分,因此它的文档也不在 Nix 包管理器的文档中。 +另外 NixOS 与 Home Manager 都是基于 Nixpkgs 的模块系统实现的。 + +## 模块系统有什么用? + +我们作为一个普通用户,使用 NixOS 与 Home Manager 基于模块系统实现的各种 options 就已经能满足我们大部分的需求了。 +那么深入学习模块系统对于我们来说,还有什么好处呢? + +我们在前面介绍配置的模块化时,提到了核心点是将配置拆分为多个模块,再通过 `imports = [ ... ];` 来导入这些模块。这其实就是模块系统最基础的用法。 +但仅仅使用 `imports = [ ... ];`,我们只能将模块中定义的配置原封不动地导入到当前模块中,无法对其做任何定制,灵活性很差。 +在配置简单的情况下,这种方式已经足够了,但如果我们的配置比较复杂,那么这种方式就显得力不从心了。 + +这里举个例子来说明其弊端, +譬如说我通过一份配置管理了 A、B、C 跟 D 共 4 台 NixOS 主机,我希望能在尽量减少配置重复的前提下实现如下功能: + +- A、B、C 跟 D 都需要启用 docker 服务,设置开机自启 +- A 需要将 docker 的存储驱动改为 `btrfs`,其他不变 +- B、C 是位于中国的服务器,需要在 docker 配置中设置国内镜像源 +- C 是位于美国的服务器,无特殊要求 +- D 是桌面主机,需要为 docker 设置 HTTP 代理加速下载 + +如果单纯使用 `imports`,那么我们可能得将配置拆分成如下几个模块,然后在每台主机上导入不同的模块: + +```bash +› tree +. +├── docker-default.nix # 基础的 docker 配置,包含开机自启 +├── docker-btrfs.nix # 导入了 docker-default.nix,将存储驱动改为 btrfs +├── docker-china.nix # 导入了 docker-default.nix,设置国内镜像源 +└── docker-proxy.nix # 导入了 docker-default.nix,设置 HTTP 代理 +``` + +是否感觉到这样的配置很冗余?这还是一个简单的例子,如果我们的机器更多,不同机器的配置差异更大,那么这种配置的冗余性就会更加明显。 + +显然,我们需要借助其他的手段来解决这个配置冗余的问题,自定义一些我们自己的 `options` 就是一个很不错的选择。 + +在深入学习模块系统之前,我再强调一下,如下内容不是必须学习与使用的,有很多 NixOS 用户并未自定义任何 `options`,只是简单地使用 `imports` 就能满足他们的需求了。 +如果你是新手,可以考虑在遇到类似上面这种,`imports` 解决不了的问题时再来学习这部分内容,这是完全 OK 的。 + +## 基本结构与用法 + +Nixpkgs 中定义的模块,其基本结构如下: + +```nix +{ config, pkgs, ... }: + +{ + imports = + [ # import other modules here + ]; + + options = { + # ... + }; + + config = { + # ... + }; +} +``` + +其中的 `imports = [ ... ];` 我们已经很熟悉了,但另外两个部分,我们还没有接触过,这里简单介绍下: + +- `options = { ... };`: 它类似编程语言中的变量声明,用于声明一些可配置的选项。 +- `config = { ... };`: 它类似编程语言中的变量赋值,用于为 `options` 中声明的选项赋值。 + +最典型的用法是:在同一 Nixpkgs 模块中,依据 `options = { ... };` 中声明的 `options` 当前的值,在 `config = { .. };` 设置为其他 `options` 赋值,这样就实现了参数化配置的功能。 + +直接看个例子更容易理解: + +```nix +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.foo; +in { + options.programs.foo = { + enable = mkEnableOption "the foo program"; + + package = mkOption { + type = types.package; + default = pkgs.jq; + defaultText = literalExpression "pkgs.foo"; + description = "foo package to use."; + }; + + extraConfig = mkOption { + default = ""; + example = '' + foo bar + ''; + type = types.lines; + description = '' + Extra settings for foo. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + xdg.configFile."foo/foorc" = mkIf (cfg.extraConfig != "") { + text = '' + # Generated by Home Manager. + + ${cfg.extraConfig} + ''; + }; + }; +} +``` + +上面这个模块定义了三个 `options`: + +- `programs.foo.enable`: 用于控制是否启用此模块 +- `programs.foo.package`: 用于自定义 foo 这个包,比如说使用不同版本、设置不同编译参数等等。 +- `programs.foo.extraConfig`: 用于自定义 foo 的配置文件。 + +然后在 `config` 中,根据 `options` 中声明的这三个变量的值,做了不同的设置: + +- 如果 `programs.foo.enable` 为 `false` 或者未定义,则不做任何设置。 + - 这是借助 `lib.mkIf` 实现的。 +- 否则 + - 将 `programs.foo.package` 添加到 `home.packages` 中,以将其安装到用户环境中。 + - 将 `programs.foo.extraConfig` 的值写入到 `~/.config/foo/foorc` 中。 + +这样,我们就可以在另一个 nix 文件中导入这个模块,并通过设置这里定义的 `options` 来实现对 foo 的自定义配置了,示例: + +```nix +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./foo.nix + ]; + + programs.foo ={ + enable = true; + package = pkgs.foo; + extraConfig = '' + foo baz + ''; + }; +} +``` + +上面这个例子中我们为 `options` 赋值的方式实际上是一种**缩写**,当一个模块中只声明了 `options`,而没有声明 `config` (以及其他模块系统的特殊参数)时,我们可以省略掉 `config` 前缀,直接使用 `options` 的名称进行赋值。 + +## 模块系统的赋值与延迟求值 + +模块系统充分利用了 Nix 的延迟求值特性,这也是它能实现参数化配置的关键。 + +先看个简单的例子: + +```nix +{ + description = "NixOS Flake for Test"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + home-manager = { + url = "github:nix-community/home-manager/release-23.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = {nixpkgs, ...}: { + nixosConfigurations = { + "test" = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ({ + config, + lib, + ... + }: { + options = { + foo = lib.mkOption { + default = false; + type = lib.types.bool; + }; + }; + + # 示例 1(正常) + config.warnings = if config.foo then ["foo"] else []; + + # 示例 2(无限递归) + # error: infinite recursion encountered + # config = if config.foo then { warnings = ["foo"];} else {}; + + # 示例 3(正常) + # config = lib.mkIf config.foo {warnings = ["foo"];}; + }) + ]; + }; + }; + }; +} +``` + +上述配置中的示例 1、2、3 中,`config.warnings` 的值都依赖于 `config.foo` 的值,但它们的实现方式却不同。 +将上述配置保存为 `flake.nix`,然后使用命令 `nix eval .#nixosConfigurations.test.config.warnings` 分别测试示例 1、2、3, +可以发现示例 1、3 都能正常工作,而示例 2 则会报错 `error: infinite recursion encountered`。 + +下面分别解释说明下: + +1. 示例一计算流程:`config.warnings` => `config.foo` => `config` + 1. 首先,Nix 尝试计算 `config.warnings` 的值,但发现它依赖于 `config.foo`. + 2. 接着,Nix 尝试计算 `config.foo` 的值,它依赖于其外层的 `config`. + 3. Nix 尝试计算 `config` 的值,`config` 中未被 `config.foo` 真正使用的内容都会被 Nix 延迟求值,因此这里不会递归依赖 `config.warnings`。 + 4. `config.foo` 求值结束,接着 `config.warnings` 被赋值,计算结束。 +2. 示例二:`config` => `config.foo` => `config` + 1. 首先,Nix 尝试计算 `config` 的值,但发现它依赖于 `config.foo`. + 2. 接着,Nix 尝试计算 `config.foo` 的值,它依赖于其外层的 `config`. + 3. Nix 尝试计算 `config` 的值,这又跳转到步骤 1,于是进入无限递归,最终报错。 +3. 示例三:跟示例二唯一的区别是改用了 `lib.mkIf` 解决了无限递归问题。 + +其关键就在于 `lib.mkIf` 这个函数,使用它定义的 `config` 会被 Nix 延迟求值,也就是说会在 `config.foo` 求值结束后,才会真正计算 `config = lib.mkIf ...` 的值。 + +Nixpkgs 中的模块系统提供了一系列类似 `lib.mkIf` 的函数,用于实现参数化配置与智能的模块合并: + +1. `lib.mkIf`: 上面已经介绍过了。 +1. `lib.mkOverride` / `lib.mkdDefault` / `lib.mkForce`: 在前面 [模块化 NixOS 配置](../nixos-with-flakes/modularize-the-configuration.md) 中已经介绍过了。 +1. `lib.mkOrder`, `lib.mkBefore` 与 `lib.mkAfter`: 同上 +1. 查看 [Option Definitions - NixOS][Option Definitions - NixOS] 了解更多与 options 赋值(definition)相关的函数。 + + +## Options 声明与类型检查 + +模块系统的赋值是我们最常用的功能,而如果我们需要自定义一些 `options`,还需要深入了解下 options 的声明与类型检查。 + +这个我觉得就还挺简单的,比赋值要简单挺多了,直接看官方文档就能懂个大概,这里就不再赘述了: + +- [Option Declarations - NixOS][Option Declarations - NixOS] +- [Options Types - NixOS][Options Types - NixOS] + +## References + +- [Best resources for learning about the NixOS module system? - Discourse](https://discourse.nixos.org/t/best-resources-for-learning-about-the-nixos-module-system/1177/4) +- [NixOS modules - NixOS Wiki](https://nixos.wiki/wiki/NixOS_modules) +- [NixOS: config argument - NixOS Wiki](https://nixos.wiki/wiki/NixOS:config_argument) +- [Module System - Nixpkgs][Module System - Nixpkgs] +- [Writing NixOS Modules - Nixpkgs][Writing NixOS Modules - Nixpkgs] + + +[lib/modules.nix]: https://github.com/NixOS/nixpkgs/blob/23.11/lib/modules.nix#L995 +[Module System - Nixpkgs]: https://github.com/NixOS/nixpkgs/blob/23.11/doc/module-system/module-system.chapter.md +[Writing NixOS Modules - Nixpkgs]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/writing-modules.chapter.md +[Option Definitions - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-def.section.md +[Option Declarations - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-declarations.section.md +[Options Types - NixOS]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-types.section.md diff --git a/docs/zh/other-usage-of-flakes/options.md b/docs/zh/other-usage-of-flakes/options.md deleted file mode 100644 index 0935000..0000000 --- a/docs/zh/other-usage-of-flakes/options.md +++ /dev/null @@ -1,8 +0,0 @@ -# Nix option difinitions - -TODO - -## References - -- [NixOS option definitions - NixOS in Production](https://github.com/Gabriella439/nixos-in-production/blob/ffa037c/manuscript/Modules.md) -- [Advanced option definitions - NixOS in Production](https://github.com/Gabriella439/nixos-in-production/blob/ffa037ce7584bd1138d0f16df69821a8eef42b44/manuscript/AdvancedModules.md)