nixos-and-flakes-book/docs/nixos-with-flakes/nixos-with-flakes-enabled.md
2024-03-16 12:32:55 +08:00

435 lines
22 KiB
Markdown

# Enabling NixOS with Flakes
## Enabling Flakes Support for NixOS {#enable-nix-flakes}
Compared to the default configuration method currently used in NixOS, Flakes offers better reproducibility. Its clear package structure definition inherently supports dependencies on other Git repositories, facilitating code sharing. Therefore, this book suggests using Flakes to manage system configurations. Currently, Flakes is still an experimental feature and not enabled by default. We need to manually modify the `/etc/nixos/configuration.nix` file to enable the Flakes feature and the accompanying new nix command-line tool:
```nix{12,16}
{ config, pkgs, ... }:
{
imports = [
# Include the results of the hardware scan.
./hardware-configuration.nix
];
# ......
# Enable the Flakes feature and the accompanying new nix command-line tool
nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [
# Flakes clones its dependencies through the git command,
# so git must be installed first
git
vim
wget
curl
];
# Set the default editor to vim
environment.variables.EDITOR = "vim";
# ......
}
```
After making these changes, run `sudo nixos-rebuild switch` to apply the modifications. Then, you can use the Flakes feature to manage your system configuration.
The new nix command-line tool also offers some convenient features. For example, you can now use the `nix repl` command to open a nix interactive environment.
If you're interested, you can use it to review and test all the Nix syntax you've learned before.
## Switching System Configuration to `flake.nix` {#switch-to-flake-nix}
After enabling the Flakes feature, the `sudo nixos-rebuild switch` command will prioritize reading the `/etc/nixos/flake.nix` file, and if it's not found, it will attempt to use `/etc/nixos/configuration.nix`.
You can start by using the official templates to learn how to write a flake.
First, check what templates are available:
```bash
nix flake show templates
```
Among them, the `templates#full` template demonstrates all possible usage. Take a look at its content:
```bash
nix flake init -t templates#full
cat flake.nix
```
Referencing this template, create the file `/etc/nixos/flake.nix` and write the configuration content. All subsequent system modifications will be taken over by Nix Flakes.
Here's an example of the content:
```nix{16}
{
description = "A simple NixOS flake";
inputs = {
# NixOS official package source, using the nixos-23.11 branch here
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
};
outputs = { self, nixpkgs, ... }@inputs: {
# Please replace my-nixos with your hostname
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Import the previous configuration.nix we used,
# so the old configuration file still takes effect
./configuration.nix
];
};
};
}
```
Here we defined a system named `my-nixos`, with its configuration file located at `/etc/nixos/` as `./configuration.nix`. This means we are still using the old configuration.
Now, when you execute `sudo nixos-rebuild switch` to apply the configuration, the system should not change at all because we have simply switched to using Nix Flakes, and the configuration content remains consistent with before.
After the switch, we can manage the system through the Flakes feature.
Currently, our flake includes these files:
- `/etc/nixos/flake.nix`: The entrypoint for the flake, which is recognized and deployed when `sudo nixos-rebuild switch` is executed.
- `/etc/nixos/flake.lock`: The automatically generated version lock file, which records the data sources, hash values, and version numbers of all inputs in the entire flake, ensuring system reproducibility.
- `/etc/nixos/configuration.nix`: This is our previous configuration file, which is imported as a module in `flake.nix`. Currently, all system configurations are written in this file.
- `/etc/nixos/hardware-configuration.nix`: This is the system hardware configuration file, generated by NixOS, which describes the system's hardware information.
Up to this point, `/etc/nixos/flake.nix` has merely been a thin wrapper around `/etc/nixos/configuration.nix`, offering no new functionality and introducing no disruptive changes. In the content of the book that follows, we will gradually see the benefits that such a wrapper brings.
> Note: The configuration management method described in this book is NOT "Everything in a single file". It is recommended to categorize configuration content into different nix files, then introduce these configuration files in the `modules` list of `flake.nix`, and manage them with Git.
>
> The benefits of this approach are better organization of configuration files and improved maintainability of the configuration. The section [Modularizing NixOS Configuration](./modularize-the-configuration.md) will explain in detail how to modularize your NixOS configuration, and [Other Useful Tips - Managing NixOS Configuration with Git](./other-useful-tips.md) will introduce several best practices for managing NixOS configuration with Git.
## `flake.nix` Configuration Explained {#flake-nix-configuration-explained}
Above, we created a `flake.nix` file to manage system configurations, but you might still be unclear about its structure. Let's explain the content of this file in detail.
### 1. Flake Inputs
First, let's look at the `inputs` attribute. It is an attribute set that defines all the dependencies of this flake. These dependencies will be passed as arguments to the `outputs` function after they are fetched:
```nix{2-5,7}
{
inputs = {
# NixOS official package source, using the nixos-23.11 branch here
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
};
outputs = { self, nixpkgs, ... }@inputs: {
# Omitting previous configurations......
};
}
```
Dependencies in `inputs` has many types and definitions.
It can be another flake, a regular Git repository, or a local path.
The section [Other Usage of Flakes - Flake Inputs](../other-usage-of-flakes/inputs.md) describes common types of dependencies and their definitions in detail.
Here we only define a dependency named `nixpkgs`, which is the most common way to reference in a flake, i.e., `github:owner/name/reference`. The `reference` here can be a branch name, commit-id, or tag.
After `nixpkgs` is defined in `inputs`, you can use it in the parameters of the subsequent `outputs` function, which is exactly what our example does.
### 2. Flake Outputs
Now let's look at `outputs`.
It is a function that takes the dependencies from `inputs` as its parameters, and its return value is an attribute set, which represents the build results of the flake:
```nix{11-19}
{
description = "A simple NixOS flake";
inputs = {
# NixOS official package source, here using the nixos-23.11 branch
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
};
# The `self` parameter is special, it refers to
# the attribute set returned by the `outputs` function itself.
outputs = { self, nixpkgs, ... }@inputs: {
# The host with the hostname `my-nixos` will use this configuration
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
];
};
};
}
```
Flakes can have various purposes and can have different types of outputs. The section [Flake Outputs](../other-usage-of-flakes/outputs.md) provides a more detailed introduction.
Here, we are only using the `nixosConfigurations` type of outputs, which is used to configure NixOS systems.
When we run the `sudo nixos-rebuild switch` command, it looks for the `nixosConfigurations.my-nixos` attribute (where `my-nixos` will be the hostname of your current system) in the attribute set returned by the `outputs` function of `/etc/nixos/flake.nix` and uses the definition there to configure your NixOS system.
Actually, we can also customize the location of the flake and the name of the NixOS configuration instead of using the defaults.
This can be done by adding the `--flake` parameter to the `nixos-rebuild` command. Here's an example:
```nix
sudo nixos-rebuild switch --flake /path/to/your/flake#your-hostname
```
A brief explanation of the `--flake /path/to/your/flake#your-hostname` parameter:
1. `/path/to/your/flake` is the location of the target flake. The default path is `/etc/nixos/`.
2. `#` is a separator, and `your-hostname` is the name of the NixOS configuration. `nixos-rebuild` will default to using the hostname of your current system as the configuration name to look for.
You can even directly reference a remote GitHub repository as your flake source, for example:
```nix
sudo nixos-rebuild switch --flake github:owner/repo#your-hostname
```
### 3. Simple Introduction to `nixpkgs.lib.nixosSystem` Function {#simple-introduction-to-nixpkgs-lib-nixos-system}
**A Flake can depend on other Flakes to utilize the features they provide.**
By default, a flake searches for a `flake.nix` file in the root directory of each of its dependencies (i.e., each item in `inputs`) and lazily evaluates their `outputs` functions. It then passes the attribute set returned by these functions as arguments to its own `outputs` function, enabling us to use the features provided by the other flakes within our current flake.
More precisely, the evaluation of the `outputs` function for each dependency is lazy. This means that a flake's `outputs` function is only evaluated when it is actually used, thereby avoiding unnecessary calculations and improving efficiency.
The description above may be a bit confusing, so let's take a look at the process with the `flake.nix` example used in this section.
Our `flake.nix` declares the `inputs.nixpkgs` dependency, so that [nixpkgs/flake.nix] will be evaluated when we run the `sudo nixos-rebuild switch` command.
From the source code of the Nixpkgs repository, we can see that its flake outputs definition includes the `lib` attribute, and in our example, we use the `lib` attribute's `nixosSystem` function to configure our NixOS system:
```nix{8-13}
{
inputs = {
# NixOS official package source, here using the nixos-23.11 branch
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
];
};
};
}
```
The attribute set following `nixpkgs.lib.nixosSystem` is the function's parameter. We have only set two parameters here:
1. `system`: This is straightforward, it's the system architecture parameter.
2. `modules`: This is a list of modules, where the actual NixOS system configuration is defined.
The `/etc/nixos/configuration.nix` configuration file itself is a Nixpkgs Module, so it can be directly added to the `modules` list for use.
Understanding these basics is sufficient for beginners. Exploring the `nixpkgs.lib.nixosSystem` function in detail requires a grasp of the Nixpkgs module system.
Readers who have completed the [Modularizing NixOS Configuration](./modularize-the-configuration.md) section can return to [nixpkgs/flake.nix] to find the definition of `nixpkgs.lib.nixosSystem`, trace its source code, and study its implementation.
## Nixpkgs Module Structure Explained {#simple-introduction-to-nixpkgs-module-structure}
> The detailed workings of this module system will be introduced in the following [Modularizing NixOS Configuration](./modularize-the-configuration.md) section. Here, we'll just cover some basic knowledge.
You might be wondering why the `/etc/nixos/configuration.nix` configuration file adheres to the Nixpkgs Module definition and can be referenced directly within the `flake.nix`.
This is because the Nixpkgs repository contains a significant amount of NixOS implementation source code, primarily written in Nix. To manage and maintain such a large volume of Nix code and to allow users to customize various functions of their NixOS systems, a modular system for Nix code is essential.
This modular system for Nix code is also implemented within the Nixpkgs repository and is primarily used for modularizing NixOS system configurations. However, it is also widely used in other contexts, such as nix-darwin and home-manager.
Since NixOS is built on this modular system, it is only natural that its configuration files, including `/etc/nixos/configuration.nix`, are Nixpkgs Modules.
Before delving into the subsequent content, it's essential to have a basic understanding of how this module system operates.
Here's a simplified structure of a Nixpkgs Module:
```nix
{lib, config, options, pkgs, ...}:
{
# Importing other Modules
imports = [
# ...
./xxx.nix
];
for.bar.enable = true;
# Other option declarations
# ...
}
```
The definition is actually a Nix function, and it has five **automatically generated, automatically injected, and declaration-free parameters** provided by the module system:
1. `lib`: A built-in function library included with nixpkgs, offering many practical functions for operating Nix expressions.
- For more information, see <https://nixos.org/manual/nixpkgs/stable/#id-1.4>.
2. `config`: A set of all options' values in the current environment, which will be used extensively in the subsequent section on the module system.
3. `options`: A set of all options defined in all Modules in the current environment.
4. `pkgs`: A collection containing all nixpkgs packages, along with several related utility functions.
- At the beginner stage, you can consider its default value to be `nixpkgs.legacyPackages."${system}"`, and the value of `pkgs` can be customized through the `nixpkgs.pkgs` option.
5. `modulesPath`: A parameter available only in NixOS, which is a path pointing to [nixpkgs/nixos/modules](https://github.com/NixOS/nixpkgs/tree/nixos-23.11/nixos/modules).
- It is defined in [nixpkgs/nixos/lib/eval-config-minimal.nix#L43](https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/lib/eval-config-minimal.nix#L43).
- It is typically used to import additional NixOS modules and can be found in most NixOS auto-generated `hardware-configuration.nix` files.
## Passing Non-default Parameters to Submodules {#pass-non-default-parameters-to-submodules}
If you need to pass other non-default parameters to submodules, you will need to use some special methods to manually specify these non-default parameters.
The Nixpkgs module system provides two ways to pass non-default parameters:
1. The `specialArgs` parameter of the `nixpkgs.lib.nixosSystem` function
2. Using the `_module.args` option in any module to pass parameters
The official documentation for these two parameters is buried deep and is vague and hard to understand. If readers are interested, I will include the links here:
1. `specialArgs`: There are scattered mentions related to it in the NixOS Manual and the Nixpkgs Manual.
- Nixpkgs Manual: [Module System - Nixpkgs]
- NixOS Manual: [nixpkgs/nixos-23.11/nixos/doc/manual/development/option-types.section.md#L237-L244]
1. `_module.args`: Its only official documentation is in the source code below.
- [nixpkgs/nixos-23.11/lib/modules.nix - _module.args]
In short, `specialArgs` and `_module.args` both require an attribute set as their value, and they serve the same purpose, passing all parameters in the attribute set to all submodules. The difference between them is:
1. The `_module.args` option can be used in any module to pass parameters to each other, which is more flexible than `specialArgs`, which can only be used in the `nixpkgs.lib.nixosSystem` function.
1. `_module.args` is declared within a module, so it must be evaluated after all modules have been evaluated before it can be used. This means that **if you use the parameters passed through `_module.args` in `imports = [ ... ];`, it will result in an `infinite recursion` error**. In this case, you must use `specialArgs` instead.
The NixOS community generally recommends prioritizing the use of the `_module.args` option and resorting to `specialArgs` only when `_module.args` cannot be used.
Suppose you want to pass a certain dependency to a submodule for use. You can use the `specialArgs` parameter to pass the `inputs` to all submodules:
```nix{13}
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
another-input.url = "github:username/repo-name/branch-name";
};
outputs = inputs@{ self, nixpkgs, another-input, ... }: {
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# Set all inputs parameters as special arguments for all submodules,
# so you can directly use all dependencies in inputs in submodules
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
];
};
};
}
```
Or you can achieve the same effect using the `_module.args` option:
```nix{14}
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
another-input.url = "github:username/repo-name/branch-name";
};
outputs = inputs@{ self, nixpkgs, another-input, ... }: {
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
{
# Set all inputs parameters as special arguments for all submodules,
# so you can directly use all dependencies in inputs in submodules
_module.args = { inherit inputs; };
}
];
};
};
}
```
Choose one of the two methods above to modify your configuration, and then you can use the `inputs` parameter in `/etc/nixos/configuration.nix`. The module system will automatically match the `inputs` defined in `specialArgs` and inject it into all submodules that require this parameter:
```nix{3}
# Nix will match by name and automatically inject the inputs
# from specialArgs/_module.args into the third parameter of this function
{ config, pkgs, inputs, ... }:
{
# ...
}
```
The next section will demonstrate how to use `specialArgs`/`_module.args` to install system software from other flake sources.
## Installing System Software from Other Flake Sources {#install-system-packages-from-other-flakes}
The most common requirement for managing a system is to install software, and we have already seen in the previous section how to install packages from the official nixpkgs repository using `environment.systemPackages`. These packages all come from the official nixpkgs repository.
Now, we will learn how to install software packages from other flake sources, which is much more flexible than installing directly from nixpkgs. The main use case is to install the latest version of a software that is not yet added or updated in Nixpkgs.
Taking the Helix editor as an example, here's how to compile and install the master branch of Helix directly.
First, add the helix input data source to `flake.nix`:
```nix{6,12,18}
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
# helix editor, use the master branch
helix.url = "github:helix-editor/helix/master";
};
outputs = inputs@{ self, nixpkgs, ... }: {
nixosConfigurations.my-nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
# This module works the same as the `specialArgs` parameter we used above
# chose one of the two methods to use
# { _module.args = { inherit inputs; };}
];
};
};
}
```
Next, you can reference this flake input data source in `configuration.nix`:
```nix{1,10}
{ config, pkgs, inputs, ... }:
{
# ...
environment.systemPackages = with pkgs; [
git
vim
wget
curl
# Here, the helix package is installed from the helix input data source
inputs.helix.packages."${pkgs.system}".helix
];
# ...
}
```
Make the necessary changes and deploy with `sudo nixos-rebuild switch`. The deployment will take much longer this time because Nix will compile the entire Helix program from source.
After deployment, you can directly test and verify the installation using the `hx` command in the terminal.
Additionally, if you just want to try out the latest version of Helix and decide whether to install it on your system later, there is a simpler way to do it in one command (but as mentioned earlier, compiling from source will take a long time):
```bash
nix run github:helix-editor/helix/master
```
We will go into more detail on the usage of `nix run` in the following section [Usage of the New CLI](../other-usage-of-flakes/the-new-cli.md).
## Leveraging Features from Other Flakes Packages
In fact, this is the primary functionality of Flakes — a flake can depend on other flakes, allowing it to utilize the features they provide. It's akin to how we incorporate functionalities from other libraries when writing programs in TypeScript, Go, Rust, and other programming languages.
The example above, using the latest version from the official Helix Flake, illustrates this functionality. More use cases will be discussed later, and here are a few examples referenced for future mention:
- [Getting Started with Home Manager](./start-using-home-manager.md): This introduces the community's Home-Manager as a dependency, enabling direct utilization of the features provided by this Flake.
- [Downgrading or Upgrading Packages](./downgrade-or-upgrade-packages.md): Here, different versions of Nixpkgs are introduced as dependencies, allowing for flexible selection of packages from various versions of Nixpkgs.
[nixpkgs/flake.nix]: https://github.com/NixOS/nixpkgs/tree/nixos-23.11/flake.nix
[nixpkgs/nixos/lib/eval-config.nix]: https://github.com/NixOS/nixpkgs/tree/nixos-23.11/nixos/lib/eval-config.nix
[Module System - Nixpkgs]: https://github.com/NixOS/nixpkgs/blob/23.11/doc/module-system/module-system.chapter.md
[nixpkgs/nixos-23.11/lib/modules.nix - _module.args]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/lib/modules.nix#L122-L184
[nixpkgs/nixos-23.11/nixos/doc/manual/development/option-types.section.md#L237-L244]: https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/doc/manual/development/option-types.section.md?plain=1#L237-L244