feat: migrate the content from thiscute.world into the repo

This commit is contained in:
Ryan Yin 2023-06-23 18:05:06 +08:00
parent 9cc4ece151
commit 375603d09e
31 changed files with 4379 additions and 37 deletions

View file

@ -2,15 +2,6 @@
An unofficial NixOS & Flakes :book: for beginners: https://nixos-and-flakes.thiscute.world
## Warning about Flakes
The flakes experimental feature is a major development for Nix, it introduces a policy for managing dependencies between Nix expressions, it improves reproducibility, composability and usability in the Nix ecosystem. Although it's still an experimental feature, flakes have been widely used by the Nix community.[^1]
It's Flakes is one of the most significant changes the nix project has ever seen.[^2]
:warning: But **Flakes is still an experimental feature**, there are still many problems with it, and it is likely to introduce some breaking changes in the process of stablizing it, and its uncertain how greatly the breaking changes will be, so **use it at your own risk**!
## Contribution
> _A real community, however, exists only when its members interact in a meaningful way that deepens their understanding of each other and leads to learning._
@ -24,7 +15,3 @@ Thank you to [all the people](https://github.com/ryan4yin/nixos-and-flakes-book/
## License
[MIT](https://opensource.org/licenses/MIT)
[^1]: [Flakes - NixOS Wiki](https://nixos.wiki/index.php?title=Flakes)
[^2]: [Flakes are such an obviously good thing](https://grahamc.com/blog/flakes-are-an-obviously-good-thing/)

View file

@ -9,6 +9,43 @@ export default defineConfig({
link: "/",
title: "NixOS & Flakes Book",
description: "An unofficial book for beginners",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Home", link: "/" },
{ text: "Get Started", link: "/introduction/index.md" },
{ text: "Best Pracetice", link: "/best-practices/index.md" },
],
sidebar: [
{
text: "Get Started",
items: [
{ text: "Introduction", link: "/introduction/index.md" },
{
text: "Advantages and Disadvantages",
link: "/introduction/advantages-and-disadvantages.md",
},
{
text: "Installation",
link: "/introduction/installation.md",
},
],
},
{
text: "The Nix Language",
items: [{ text: "Basics", link: "/the-nix-language/index.md" }],
},
],
socialLinks: [
{
icon: "github",
link: "https://github.com/ryan4yin/nixos-and-flakes-book",
},
],
},
},
zh: {
label: "简体中文",
@ -16,29 +53,43 @@ export default defineConfig({
link: "/zh/",
title: "NixOS 与 Flakes",
description: "一份非官方的新手指南",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Home", link: "/zh/" },
{ text: "Get Started", link: "/zh/introduction/index.md" },
{ text: "Best Pracetice", link: "/zh/best-practices/index.md" },
],
sidebar: [
{
text: "开始使用",
items: [
{ text: "简介", link: "/zh/introduction/index.md" },
{
text: "优缺点",
link: "/zh/introduction/advantages-and-disadvantages.md",
},
{
text: "安装",
link: "/zh/introduction/installation.md",
},
],
},
{
text: "Nix 语言",
items: [{ text: "快速入门", link: "/zh/the-nix-language/index.md" }],
},
],
socialLinks: [
{
icon: "github",
link: "https://github.com/ryan4yin/nixos-and-flakes-book",
},
],
},
},
},
// shared properties and other top-level stuff...
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Home", link: "/" },
{ text: "Get Started", link: "/get-started.md" },
],
sidebar: [
{
text: "NixOS & Flakes",
items: [{ text: "Get Started", link: "/get-started.md" }],
},
],
socialLinks: [
{
icon: "github",
link: "https://github.com/ryan4yin/nixos-and-flakes-book",
},
],
},
});

View file

@ -0,0 +1,24 @@
After becoming familiar with the NixOS, you can further explore Nix's three manuals and other docs to discover more ways to use it:
- [Nix Reference Manual](https://nixos.org/manual/nix/stable/package-management/profiles.html): A guide to the Nix package manager, which mainly covers the design of the package manager and instructions for using it from the command line.
- [nixpkgs Manual](https://nixos.org/manual/nixpkgs/unstable/): A manual that introduces parameters of Nixpkgs, how to use, modify, and package Nix packages.
- [NixOS Manual](https://nixos.org/manual/nixos/unstable/): A user manual for NixOS, mainly including configuration instructions for system-level components such as Wayland/X11 and GPU.
- [nix-pills](https://nixos.org/guides/nix-pills): Nix Pills provides an in-depth explanation of how to use Nix to build software packages. It is written in a clear and understandable way and is worth reading, as it is also sufficiently in-depth.
After becoming familiar with Flakes, you may want to try some advanced techniques. Here are some popular community projects to try:
- [flake-parts](https://github.com/hercules-ci/flake-parts): Simplify the writing and maintenance of configuration through the Module module system.
- [flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus): A third-party package for simplifying Flake configuration, which is apparently more powerful.
- [digga][digga]: A large and comprehensive Flake template that combines the functionality of various useful Nix toolkits, but has a complex structure and requires some experience to navigate.
- etc.
And many other useful community projects to explore, here are some of them:
- [dev-templates](https://github.com/the-nix-way/dev-templates): Dev environments for numerous languages based on Nix flakes.
- [devenv](https://github.com/cachix/devenv): development environment management
- [agenix](https://github.com/ryantm/agenix): secrets management
- [colmena](https://github.com/zhaofengli/colmena): NixOS deployment tools
- [nixos-generator](https://github.com/nix-community/nixos-generators): generate iso/qcow2/... from nixos configuration
- [lanzaboote](https://github.com/nix-community/lanzaboote): enable secure boot for NixOS
- [impermanence](https://github.com/nix-community/impermanence): used to make NixOS stateless, to imporve the reproduciability of NixOS system.

View file

@ -0,0 +1,351 @@
Nix is powerful and flexible, it provides a lot of ways to do things, making it difficult to find the most suitable way to do your job.
Here are some best practices that I've learned from the community, hope it can help you.
## 1. Run downloaded binaries on NixOS
NixOS does not follow the FHS standard, so the binaries you download from the Internet will not likely work on NixOS. But there are some ways to make it work.
Here is a detailed guide which provides 10 ways to run downloaded binaries on NixOS: [Different methods to run a non-nixos executable on Nixos](https://unix.stackexchange.com/questions/522822/different-methods-to-run-a-non-nixos-executable-on-nixos), I recommend you to read it.
Among these methods, I prefer creating a FHS environment to run the binary, which is very convenient and easy to use.
To create such an environment, add the following code to one of your nix modules:
```nix
{ config, pkgs, lib, ... }:
{
# ......omit many configurations
environment.systemPackages = with pkgs; [
# ......omit many packages
# create a fhs environment by command `fhs`, so we can run non-nixos packages in nixos!
(pkgs.buildFHSUserEnv (base // {
name = "fhs";
targetPkgs = pkgs: (
# pkgs.buildFHSUserEnv provides only a minimal fhs environment,
# it lacks many basic packages needed by most softwares.
# so we need to add them manually.
#
# pkgs.appimageTools provides basic packages needed by most softwares.
(pkgs.appimageTools.defaultFhsEnvArgs.targetPkgs pkgs) ++ with pkgs; [
pkg-config
ncurses
# feel free to add more packages here, if you need
]
);
profile = "export FHS=1";
runScript = "bash";
extraOutputsToInstall = ["dev"];
}))
];
# ......omit many configurations
}
```
after applying the updated configuration, you can run `fhs` to enter the FHS environment, and then run the binary you downloaded, e.g.
```shell
# Activating FHS drops me in a shell which looks like a "normal" Linux
$ fhs
# check what we have in /usr/bin
(fhs) $ ls /usr/bin
# try to run a non-nixos binary downloaded from the Internet
(fhs) $ ./bin/code
```
## 2. check the source code and debug with `nix repl`
We've used `nix repl '<nixpkgs>'` many times to check the source code in this guide, it's really a powerful tool to help us understand how things work in Nix.
Better take a look at the help message of `nix repl`:
```
nix repl -f '<nixpkgs>'
Welcome to Nix 2.13.3. Type :? for help.
Loading installable ''...
Added 17755 variables.
nix-repl> :?
The following commands are available:
<expr> Evaluate and print expression
<x> = <expr> Bind expression to variable
:a <expr> Add attributes from resulting set to scope
:b <expr> Build a derivation
:bl <expr> Build a derivation, creating GC roots in the working directory
:e <expr> Open package or function in $EDITOR
:i <expr> Build derivation, then install result into current profile
:l <path> Load Nix expression and add it to scope
:lf <ref> Load Nix flake and add it to scope
:p <expr> Evaluate and print expression recursively
:q Exit nix-repl
:r Reload all files
:sh <expr> Build dependencies of derivation, then start nix-shell
:t <expr> Describe result of evaluation
:u <expr> Build derivation, then start nix-shell
:doc <expr> Show documentation of a builtin function
:log <expr> Show logs for a derivation
:te [bool] Enable, disable or toggle showing traces for errors
```
Some expressions that I use frequently: `:lf <ref>`, `:e <expr>`.
`:e <expr>` is very intuitive, so I won't repeat it. let's take a look at `:lf <ref>`:
```nix
# cd into my nix-config repo(you should replace it with your own nix-config repo)
cd ~/nix-config/
# enter nix repl
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# load my nix flake and add it to scope
nix-repl> :lf .
Added 16 variables.
# press <TAB> to see what we have in scope
nix-repl><TAB>
# ......omit some outputs
__isInt nixosConfigurations
__isList null
__isPath outPath
__isString outputs
__langVersion packages
# ......omit some outputs
# check what's in inputs
nix-repl> inputs.<TAB>
inputs.agenix inputs.nixpkgs
inputs.darwin inputs.nixpkgs-darwin
inputs.home-manager inputs.nixpkgs-unstable
inputs.hyprland inputs.nixpkgs-wayland
inputs.nil
inputs.nixos-generators
# check what's in inputs.nil
nix-repl> inputs.nil.packages.
inputs.nil.packages.aarch64-darwin
inputs.nil.packages.aarch64-linux
inputs.nil.packages.x86_64-darwin
inputs.nil.packages.x86_64-linux
# check the outputs of my nix flake
nix-repl> outputs.nixosConfigurations.<TAB>
outputs.nixosConfigurations.ai
outputs.nixosConfigurations.aquamarine
outputs.nixosConfigurations.kana
outputs.nixosConfigurations.ruby
nix-repl> outputs.nixosConfigurations.ai.<TAB>
outputs.nixosConfigurations.ai._module
outputs.nixosConfigurations.ai._type
outputs.nixosConfigurations.ai.class
outputs.nixosConfigurations.ai.config
outputs.nixosConfigurations.ai.extendModules
outputs.nixosConfigurations.ai.extraArgs
outputs.nixosConfigurations.ai.options
outputs.nixosConfigurations.ai.pkgs
outputs.nixosConfigurations.ai.type
nix-repl> outputs.nixosConfigurations.ai.config.
outputs.nixosConfigurations.ai.config.age
outputs.nixosConfigurations.ai.config.appstream
outputs.nixosConfigurations.ai.config.assertions
outputs.nixosConfigurations.ai.config.boot
outputs.nixosConfigurations.ai.config.console
outputs.nixosConfigurations.ai.config.containers
# ......omit other outputs
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.activation
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.activationPackage
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.emptyActivationPath
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.enableDebugInfo
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.enableNixpkgsReleaseCheck
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraActivationPath
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraBuilderCommands
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraOutputsToInstall
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraProfileCommands
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file
# ......omit other outputs
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.BROWSER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.DELTA_PAGER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.EDITOR
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.GLFW_IM_MODULE
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.MANPAGER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.QT_IM_MODULE
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.TERM
# ......omit other outputs
# check the value of `TERM`
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.TERM
"xterm-256color"
# check all files defined by `home.file`
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..bash_profile
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..bashrc
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/fcitx5/profile
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/fcitx5/profile-bak
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/config
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/i3blocks.conf
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/keybindings
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/layouts
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/scripts
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/wallpaper.png
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/rofi
#......
```
As you can see, we can check every attribute of my flake in the REPL after loading it, which is very convenient for debugging.
## 3. Remote deployment
Some tools like [NixOps](https://github.com/NixOS/nixops), [deploy-rs](https://github.com/serokell/deploy-rs), and [colmena](https://github.com/zhaofengli/colmena) can all be used to deploy NixOS configuration to remote hosts, but they are all too complicated for me, so skip them all.
`nixos-rebuild`, the tool we use to deploy NixOS configuration, also supports remote deployment through ssh protocol, which is very convenient and simple.
But `nixos-rebuild` does not support deploying with password authentication, so to use it for remote deployment, we need to:
1. Configure ssh public key authentication for the remote hosts.
2. To avoid sudo password verification failures, we need to use the `root` user to deploy, or grant the user sudo permission without password verification.
1. related issue: <https://github.com/NixOS/nixpkgs/issues/118655>
After the above configuration is completed, we can deploy the configuration to the server through the following command:
```bash
# 1. add the ssh key to ssh-agent first
ssh-add ~/.ssh/ai-idols
# 2. deploy the configuration to the remote host, using the ssh key we added in step 1
# and the username defaults to `$USER`, it's `ryan` in my case.
nixos-rebuild --flake .#aquamarine --target-host 192.168.4.1 --build-host 192.168.4.1 switch --use-remote-sudo --verbose
```
The commands above will build & deploy the configuration to aquamarine, the build process will be executed on aquamarine too,
and the `--use-remote-sudo` option indicates that we need to use sudo permission on the remote server to deploy the configuration.
If you want to build the configuration locally and deploy it to the remote server, just replace `--build-host aquamarinr` with `--build-host localhost`.
Instead of use ip address directly, we can also define some host aliases in `~/.ssh/config` or `/etc/ssh/ssh_config`, for example:
> ssh's config can be generated completely through Nix configuration, and this task is left to you.
```bash
cat ~/.ssh/config
# ......
Host ai
HostName 192.168.5.100
Port 22
Host aquamarine
HostName 192.168.5.101
Port 22
Host ruby
HostName 192.168.5.102
Port 22
Host kana
HostName 192.168.5.103
Port 22
```
Then we can use the host alias to deploy the configuration:
```bash
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo --verbose
```
## 4. Work with Makefile
> NOTE: Makefile's target name should not be the same as one of the file or directory in the current directory, otherwise the target will not be executed!
I use Makefile to manage the commands of my flake, which is very convenient.
For example, my Makefile looks like this:
```makefile
#
# NOTE: Makefile's target name should not be the same as one of the file or directory in the current directory,
# otherwise the target will not be executed!
#
############################################################################
#
# Nix commands related to the local machine
#
############################################################################
deploy:
nixos-rebuild switch --flake . --use-remote-sudo
debug:
nixos-rebuild switch --flake . --use-remote-sudo --show-trace --verbose
update:
nix flake update
history:
nix profile history --profile /nix/var/nix/profiles/system
gc:
# remove all generations older than 7 days
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d
# garbage collect all unused nix store entries
sudo nix store gc --debug
############################################################################
#
# Idols, Commands related to my remote distributed building cluster
#
############################################################################
add-idols-ssh-key:
ssh-add ~/.ssh/ai-idols
aqua: add-idols-ssh-key
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo
aqua-debug: add-idols-ssh-key
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo --show-trace --verbose
ruby: add-idols-ssh-key
nixos-rebuild --flake .#ruby --target-host ruby --build-host ruby switch --use-remote-sudo
ruby-debug: add-idols-ssh-key
nixos-rebuild --flake .#ruby --target-host ruby --build-host ruby switch --use-remote-sudo --show-trace --verbose
kana: add-idols-ssh-key
nixos-rebuild --flake .#kana --target-host kana --build-host kana switch --use-remote-sudo
kana-debug: add-idols-ssh-key
nixos-rebuild --flake .#kana --target-host kana --build-host kana switch --use-remote-sudo --show-trace --verbose
idols: aqua ruby kana
idols-debug: aqua-debug ruby-debug kana-debug
```
Save the above Makefile to the root directory of the flake, and then we can use `make deploy` to deploy the configuration to the local machine, and use `make idols` to deploy the configuration to all my remote servers.
## References
- [Tips&Tricks for NixOS Desktop - NixOS Discourse][Tips&Tricks for NixOS Desktop - NixOS Discourse]
[Tips&Tricks for NixOS Desktop - NixOS Discourse]: https://discourse.nixos.org/t/tips-tricks-for-nixos-desktop/28488

View file

@ -12,7 +12,7 @@ hero:
actions:
- theme: brand
text: Get Started
link: /get-started
link: /introduction/index.md
- theme: alt
text: View on GitHub
link: https://github.com/ryan4yin/nixos-and-flakes-book

View file

@ -0,0 +1,28 @@
### Advantages of Nix
- **Declarative configuration, Environment as Code, can be managed with Git**
- Nix Flakes lock dependences's version through a lock file named `flake.lock`, to ensure that the system is reproducible. This idea actually borrows from some package managers such as npm, cargo, etc.
- Compared with Docker, Nix Flakes provides a much stronger guarantee for the reproducibility of build results, because Dockerfile is actually an imperative configuration and there is no such thing as `flake.lock` in Docker, Docker's reproducibility relies on sharing the build result(which is MUCH MORE LARGER than Dockerfile itself) through image registry(e.g. Docker Hub).
- **Highly convenient system customization capability**
- By changing a few lines of configuration, various components of NixOS can be easily customized. This is because Nix encapsulates all the underlying complex operations in nix packages and only exports concise and necessary declarative parameters.
- Moreover, this modification is very safe. For example, switching between different desktop environments on NixOS is very simple and clean, you just need to change several lines of the configuration.
- **Rollback**: The system can be restored to any historical state at any time(except the state that are NOT managed by NixOS, such as docker containers, postgresql data, etc...), and NixOS even adds all old versions to the boot options by default to ensure that the system can be rolled back at any time even though it crashes. Therefore, NixOS is also considered one of the most stable Linux Systems.
- **No dependency conflicts**: Because each software package in Nix has a unique hash, its installation path also includes this hash value, so multiple versions can coexist.
- **The community is very active, and there are quite a few third-party projects**. The official package repository, nixpkgs, has many contributors, and many people share their Nix configuration on Github/Gitlab. After browsing through it, the entire ecosystem gives me a sense of excitement in discovering a new continent.
### Disadvantages of Nix
- **High learning curve:**: If you want the system to be completely reproducible and avoid pitfalls caused by improper use, you need to learn about the entire design of Nix and manage the system in a declarative manner. You cannot blindly use `nix-env -i` (which is similar to `apt-get install`).
- **Chaotic documentation**: Flakes is still an experimental feature, and there are currently few documents introducing it, Most of the Nix community's documentation only introduces the old cli such as `nix-env`/`nix-channel`. If you want to start learning Nix directly from Flakes, you need to refer to a large number of old documents and extract what you need from them. In addition, some of Nix's current core functions are not well-documented (such as `imports` and Nix Module System), to figure out what it does, it is best to look at the source code...
- **Relatively high disk space usage**: To ensure that the system can be rolled back at any time, Nix preserves all historical environments by default, which can take up a lot of disk space. It can be a problem especially on some resource-constrained Virtual Machines.
- **Error messages may be obscure**: Sometimes you may come across some strange error messages and don't understand what's going on.
`--show- Trace` may throw you a stack of errors that are of little help.
### Summary
Generally speaking, I think NixOS is suitable for developers who have some experience in using Linux and programming and want to have more control over their systems.
I don't recommend you getting started with NixOS if you are new to Linux, it can be a very painful journey.

View file

@ -0,0 +1,37 @@
---
footer: false
---
## Introduction to Nix & NixOS
Nix package manager is a declarative configuration management tool. Users need to declare the expected system state in some configuration, and Nix is responsible for achieving that goal.
> To put it simply, "declarative configuration" means that users only need to declare the result they want. For example, you declare that you want to replace the i3 window manager with sway, then Nix will help you achieve the goal. You don't need to worry about the underlying details (such as which packages sway needs to install, which i3-related packages need to be uninstalled, which system configuration or environment variables need to be adjusted for sway, what adjustments need to be made to the Sway parameters if an Nvidia graphics card is used, etc.), Nix will automatically handle these details for the user(prerequisite: if the nix packages related to sway & i3 are designed properly.).
NixOS, the Linux distribution built on top of the Nix package manager, can be simply described as "OS as Code", which describes the entire operating system's state using declarative Nix configuration files.
An operating system has a variety of software packages, configuration files, text/binary data, which are all the current state of the system, and declarative configuration can manage only the static part of it.
Those dynamic data (such as PostgreSQL/MySQL/MongoDB data) are obviously not manageable through a declarative configuration (you can't just delete all new postgresql data that is not declared in the configuration at every deployment).
Therefore, **NixOS actually only supports to manage part of the state of the system in a declarative way**, and all the dynamic data mentioned above, as well as all the contents in the user's home directory, are not controlled by it (so when you rollback your NixOS to the last generation, NixOS will do nothing on these content).
Although we cannot make the entire system reproducible, the `/home` directory, as an important user directory, does have many necessary configuration files.
Another important community project, [home-manager](https://github.com/nix-community/home-manager), filled this gap.
home-manager is designed to manage user-level packages & HOME directories.
Due to Nix's features such as declarative and reproducible, Nix is not only used to manage desktop environments but also widely used to manage development environments, compilation environments, cloud virtual machines, container image construction, etc. [NixOps](https://github.com/NixOS/nixops) from the Nix official and [deploy-rs](https://github.com/serokell/deploy-rs) from the community are both operations tools based on Nix.
## Why NixOS?
I heard about the Nix package manager several years ago. It uses the Nix language to describe system configuration, and the Linux distribution built on top of it, can roll back the system to any historical state at any time(In fact, only the state declared by nix configuration files can be rollback). Although it sounds impressive, it requires learning a new language and writing code to install packages, I thought it was too troublesome and didn't study it at the time.
But I recently encountered a lot of environmental problems in the process of using EndeavourOS, and I spent a lot of energy to solve them, which made me exhausted. After thinking about it carefully, I realized that the root cause was that EndeavourOS do not have any version control and rollback mechanism, which caused the system to be unable to be restored when problems occurred.
So I switched to NixOS.
I am quite satisfied with NixOS, even more than expected.
The most amazing thing is, now I can restore the entire i3 environment and my commonly used softwares on a fresh NixOS host with just one command, that's really fantastic!
The rollback capability of NixOS gave me a lot of confidence - I no longer fear breaking the system any more. I even tried a lot of new things on NixOS, such as hyprland compositor. (On EndeavourOS before, I wouldn't have dared to play with such new compositors - it would have been a big hassle if something went wrong with the system and I need to fix it manually through various tricks.)
So that's why I chose NixOS.

View file

@ -0,0 +1,16 @@
Nix can be installed in multiple ways:
1. Install on macOS/Linux/WSL as a package manager.
2. Install NixOS, it's a Linux distribution that uses Nix to manage the entire system environment.
I chose to directly install NixOS using its official ISO image, to manage the entire system through Nix as much as possible.
The installation process is simple, and I won't go into details here.
Some materials that may be useful:
1. [Official installation method of Nix](https://nixos.org/download.html): written in bash script, `nix-command` & `flakes` are still experimental features as of 2023-04-23, and need to be manually enabled.
1. You need to refer to the instructions in [Enable flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes) to enable `nix-command` & `flakes`.
2. The official installer does not provide any uninstallation method. To uninstall Nix on Linux/macOS, you need to manually delete all related files, users, and groups.
2. [The Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer): a third-party installer written in Rust, which enables `nix-command` & `flakes` by default and provides an uninstallation command.

View file

@ -0,0 +1,28 @@
### Advantages of Nix Flakes
- **Declarative configuration, Environment as Code, can be managed with Git**
- As long as the configuration files are not lost, the system can be restored to any historical state at any time(except the state that are NOT managed by NixOS, such as docker containers, postgresql data, etc...)
- Nix Flakes lock dependences's version through a lock file named `flake.lock`, to ensure that the system is reproducible, this idea actually borrows from some package managers such as npm, cargo, etc.
- Compared with Docker, Nix Flakes provides a much stronger guarantee for the reproducibility of build results, because Dockerfile is actually an imperative configuration and there is no such thing as `flake.lock` in Docker, Docker's reproducibility relies on sharing the build result(which is MUCH MORE LARGER than Dockerfile itself) through image registry(e.g. DockerHub).
- **Highly convenient system customization capability**
- By changing a few lines of configuration, various components of NixOS can be easily customized. This is because Nix encapsulates all the underlying complex operations in nix packages and only exports concise and necessary declarative parameters.
- Moreover, this modification is very safe. An example is that one NixOS user on the V2EX forum stated that "[**on NixOS, switching between different desktop environments is very simple and clean, and it is very safe. I often switch between gnome/kde/sway.**](https://www.v2ex.com/t/938569#r_13053251)"
- **Rollback**: The system can be rolled back to any historical environment at any time, and NixOS even adds all old versions to the boot options by default to ensure that the system can be rolled back at any time even though it crashes. Therefore, NixOS is also considered one of the most stable Linux Systems.
- **No dependency conflicts**: Because each software package in Nix has a unique hash, its installation path also includes this hash value, so multiple versions can coexist.
- **The community is very active, and there are quite a few third-party projects**. The official package repository, nixpkgs, has many contributors, and many people share their Nix configuration on Github/Gitlab. After browsing through it, the entire ecosystem gives me a sense of excitement in discovering a new continent.
{{< figure src="nixos-bootloader.avif" caption="All historical versions are listed in the boot options of NixOS. Image from [NixOS Discourse - 10074](https://discourse.nixos.org/t/how-to-make-uefis-grub2-menu-the-same-as-bioss-one/10074)" >}}
### Disadvantages of Nix Flakes
- **Relatively high learning curve:**: If you want the system to be completely reproducible and avoid pitfalls caused by improper use, you need to learn about the entire design of Nix and manage the system in a declarative manner. You cannot blindly use `nix-env -i` (which is similar to `apt-get install`).
- **Chaotic documentation**: Flakes is still an experimental feature, and there are currently few documents introducing it, Most of the Nix community's documentation only introduces the old cli such as `nix-env`/`nix-channel`. If you want to start learning Nix directly from Flakes, you need to refer to a large number of old documents and extract what you need from them. In addition, some of Nix's current core functions are not well-documented (such as `imports` and Nix Module System), to figure out what it does, it is best to look at the source code...
- ~~Relatively few packages~~: Retract this one. The official claim is that nixpkgs has [80000+](https://search.nixos.org/packages) packages, and indeed, most packages can be found in nixpkgs.
- **Relatively high disk space usage**: To ensure that the system can be rolled back at any time, Nix preserves all historical environments by default, which can take up a lot of disk space. Although you can manually clean up old historical environments periodically with `nix-collect-garbage`, it is still recommended to buy a larger hard drive.
### Summary
Generally speaking, I think NixOS is suitable for developers who have some experience in using Linux and programming and want to have more control over their systems.
I don't recommend you getting started with NixOS if you are new to Linux, it can be a very painful journey.

70
docs/nix-flakes/index.md Normal file
View file

@ -0,0 +1,70 @@
## Warning about Flakes
The flakes experimental feature is a major development for Nix, it introduces a policy for managing dependencies between Nix expressions, it improves reproducibility, composability and usability in the Nix ecosystem. Although it's still an experimental feature, flakes have been widely used by the Nix community.[^1]
It's Flakes is one of the most significant changes the nix project has ever seen.[^2]
The benefits of Flakes are obvious, and the entire NixOS community likes it very much. Currently, more than half of the users are using Flakes[^3], so we're pretty sure that Flakes will never be deprecated.
:warning: But **Flakes is still an experimental feature**, there are still many problems with it, and it is likely to introduce some breaking changes in the process of stablizing it, and its uncertain how greatly the breaking changes will be.
So overall, I still recommend everyone to use Flakes, but be prepared for the problems that may be caused by the upcomming breaking changes.
## III. Nix Flakes and the classic Nix
As `nix-command` & `flakes` are still experimental features, the official documentation does not cover them in detail, and the community's documentation about them is also very scattered.
However, from the perspective of reproducibility and ease of management and maintenance, the classic Nix package structure and cli are no longer recommended for use.
So I will not introduce the usage of the classic Nix. It's recommended that beginners just start with `nix-command` & `flakes` and ignore all the contents about the classic Nix.
Here are the classic Nix commands and related concepts that are no longer needed after you enabling `nix-command` and `flakes`, when searching for information, you can safely ignore them:
1. `nix-channel`: `nix-channel` is similar to other package management tools such as apt/yum/pacman, managing software package versions through stable/unstable/test channels.
1. In Flakes, the functionality of `nix-channel` is completely replaced by `inputs` in `flake.nix`.
2. `nix-env`: `nix-env` is a core command-line tool for classic Nix used to manage software packages in the user environment. It installs packages from the data sources added by `nix-channel`, so the installed package's version are influenced by the channel. Packages installed with `nix-env` are not automatically recorded in Nix's declarative configuration and are entirely outside of its control, making them difficult to reproduce on other machines. Therefore, it is not recommended to use this tool.
1. The corresponding command in Flakes is `nix profile`, it's not recommended to use it either.
3. `nix-shell`: `nix-shell` is used to create a temporary shell environment, which is useful for development and testing.
1. In Flakes, it is replaced by `nix develop` and `nix shell`.
4. `nix-build`: `nix-build` is used to build Nix packages, and it places the build results in `/nix/store`, but it does not record them in Nix's declarative configuration.
1. In Flakes, `nix-build` is replaced by `nix build`.
5. ...
> maybe `nix-env -qa` is still useful some times, which returns all packages installed in the System.
## IX. When will flakes stablized {#when-will-flakes-stablized}
I dived into some details about flakes:
- https://github.com/NixOS/rfcs/pull/136: A plan to stabilize Flakes and the new CLI incrementally, still WIP.
- https://discourse.nixos.org/t/why-are-flakes-still-experimental/29317: A post, Why are flakes still experimental?
- https://grahamc.com/blog/flakes-are-an-obviously-good-thing/: Flakes are such an obviously good thing... but the design and development process should be better.
- [Draft: 1 year roadmap - NixOS Foundation](https://nixos-foundation.notion.site/1-year-roadmap-0dc5c2ec265a477ea65c549cd5e568a9) A roadmap of nixos fundation, which includes plan about the stabilization of flakes.
After reading all of these, I feel like that flakes will eventually be stabilized in one or two years, maybe with some breaking changes.
[^1]: [Flakes - NixOS Wiki](https://nixos.wiki/index.php?title=Flakes)
[^2]: [Flakes are such an obviously good thing](https://grahamc.com/blog/flakes-are-an-obviously-good-thing/)
[^3]: [Draft: 1 year roadmap - NixOS Foundation](https://nixos-foundation.notion.site/1-year-roadmap-0dc5c2ec265a477ea65c549cd5e568a9)
[^1]: [Flakes - NixOS Wiki](https://nixos.wiki/index.php?title=Flakes)
[^2]: [Flakes are such an obviously good thing](https://grahamc.com/blog/flakes-are-an-obviously-good-thing/)

View file

@ -0,0 +1,148 @@
## VII. Usage of Nix Flakes
Up to now, we have written a lot of configuration with Flakes to manage NixOS. Here is a brief introduction to the more detailed content of the Flakes, as well as the new command lines commonly used with flakes.
### 1. Flake inputs {#flake-inputs}
The `inputs` in `flake.nix` is a attribute set, used to specify the dependencies of the current flake, there are many types of `inputs`, for example:
```nix
{
inputs = {
# use master branch of the GitHub repository as input, this is the most common input format
nixpkgs.url = "github:Mic92/nixpkgs/master";
# Git URL, can be used for any Git repository based on https/ssh protocol
git-example.url = "git+https://git.somehost.tld/user/path?ref=branch&rev=fdc8ef970de2b4634e1b3dca296e1ed918459a9e";
# The above example will also copy .git, use this for (shallow) local Git repos
git-directory-example.url = "git+file:/path/to/repo?shallow=1";
# Local directories (for absolute paths you can omit 'path:')
directory-example.url = "path:/path/to/repo";
bar = {
url = "github:foo/bar/branch";
# if the input is not a flake, you need to set flake=false
flake = false;
};
sops-nix = {
url = "github:Mic92/sops-nix";
# The `follows` keyword in inputs is used for inheritance.
# Here, `inputs.nixpkgs` of sops-nix is kept consistent with the `inputs.nixpkgs` in
# the current flake, to avoid problems caused by different versions of nixpkgs.
inputs.nixpkgs.follows = "nixpkgs";
};
# Pin flakes to a specific revision
nix-doom-emacs = {
url = "github:vlaci/nix-doom-emacs?rev=238b18d7b2c8239f676358634bfb32693d3706f3";
flake = false;
};
# To use a subdirectory of a repo, pass `dir=xxx`
nixpkgs.url = "github:foo/bar?dir=shu";
}
}
```
### 2. Flake outputs
the `outputs` in `flake.nix` are what a flake produces as part of its build. Each flake can have many different outputs simultaneously, including but not limited to:
- Nix packages: named `apps.<system>.<name>`, `packages.<system>.<name>`, or `legacyPackages.<system>.<name>`
- we can build a package by command `nix build .#<name>`
- Nix Helper Functions: named `lib`., which means a library for other flakes.
- Nix development environments: named `devShells`
- `devShells` can be used by command `nix develop`, will be introduced later.
- NixOS configuration: named `nixosConfiguration`
- `nixosConfiguration` will be used by command `nixos-rebuild switch --flake .#<name>`
- Nix templates: named `templates`
- templates can be used by command `nix flake init --template <reference>`
- Other user defined outputs, may be parsed by other nix-related tools.
An example copy from NixOS Wiki:
```nix
{ self, ... }@inputs:
{
# Executed by `nix flake check`
checks."<system>"."<name>" = derivation;
# Executed by `nix build .#<name>`
packages."<system>"."<name>" = derivation;
# Executed by `nix build .`
packages."<system>".default = derivation;
# Executed by `nix run .#<name>`
apps."<system>"."<name>" = {
type = "app";
program = "<store-path>";
};
# Executed by `nix run . -- <args?>`
apps."<system>".default = { type = "app"; program = "..."; };
# Formatter (alejandra, nixfmt or nixpkgs-fmt)
formatter."<system>" = derivation;
# Used for nixpkgs packages, also accessible via `nix build .#<name>`
legacyPackages."<system>"."<name>" = derivation;
# Overlay, consumed by other flakes
overlays."<name>" = final: prev: { };
# Default overlay
overlays.default = {};
# Nixos module, consumed by other flakes
nixosModules."<name>" = { config }: { options = {}; config = {}; };
# Default module
nixosModules.default = {};
# Used with `nixos-rebuild --flake .#<hostname>`
# nixosConfigurations."<hostname>".config.system.build.toplevel must be a derivation
nixosConfigurations."<hostname>" = {};
# Used by `nix develop .#<name>`
devShells."<system>"."<name>" = derivation;
# Used by `nix develop`
devShells."<system>".default = derivation;
# Hydra build jobs
hydraJobs."<attr>"."<system>" = derivation;
# Used by `nix flake init -t <flake>#<name>`
templates."<name>" = {
path = "<store-path>";
description = "template description goes here?";
};
# Used by `nix flake init -t <flake>`
templates.default = { path = "<store-path>"; description = ""; };
}
```
### 3. Flake Command Line Usage
after enabled `nix-command` & `flake`, you can use `nix help` to get all the info of [New Nix Commands][New Nix Commands], some useful examples are listed below:
```bash
# `nixpkgs#ponysay` means `ponysay` from `nixpkgs` flake.
# [nixpkgs](https://github.com/NixOS/nixpkgs) contains `flake.nix` file, so it's a flake.
# `nixpkgs` is a falkeregistry id for `github:NixOS/nixpkgs/nixos-unstable`.
# you can find all the falkeregistry ids at <https://github.com/NixOS/flake-registry/blob/master/flake-registry.json>
# so this command means install and run package `ponysay` in `nixpkgs` flake.
echo "Hello Nix" | nix run "nixpkgs#ponysay"
# this command is the same as above, but use a full flake URI instead of falkeregistry id.
echo "Hello Nix" | nix run "github:NixOS/nixpkgs/nixos-unstable#ponysay"
# instead of treat flake package as an application,
# this command use `devShells.example` in flake `zero-to-nix`'s outputs, to setup the development environment,
# and then open a bash shell in that environment.
nix develop "github:DeterminateSystems/zero-to-nix#example"
# instead of using a remote flake, you can open a bash shell using the flake located in the current directory.
mkdir my-flake && cd my-flake
## init a flake with template
nix flake init --template "github:DeterminateSystems/zero-to-nix#javascript-dev"
# open a bash shell using the flake in current directory
nix develop
# or if your flake has multiple devShell outputs, you can specify which one to use.
nix develop .#example
# build package `bat` from flake `nixpkgs`, and put a symlink `result` in the current directory.
mkdir build-nix-package && cd build-nix-package
nix build "nixpkgs#bat"
# build a local flake is the same as nix develop, skip it
```
[Zero to Nix - Determinate Systems][Zero to Nix - Determinate Systems] is a brand new guide to get started with Nix & Flake, recommended to read for beginners.

View file

@ -0,0 +1,956 @@
## VI. Managing the system declaratively
> https://nixos.wiki/wiki/Overview_of_the_NixOS_Linux_distribution
After learning the basics of the Nix language, we can start using it to configure our NixOS. The default configuration for NixOS is located at `/etc/nixos/configuration.nix`, which contains all the declarative configuration for the system, such as time zone, language, keyboard layout, network, users, file system, boot options, etc.
If we want to modify the system state in a reproducible way (**which is also the most recommended way**), we need to manually edit `/etc/nixos/configuration.nix`, and then execute `sudo nixos-rebuild switch` to apply the modified configuration, it will generate a new system environment based on the configuration file we modified, sets the new environment as the default one, and also preserves & added the previous environment into the boot options of grub/sytemd-boot. This ensures we can always roll back to the old environment(even if the new environment fails to start).
`/etc/nixos/configuration.nix` is the classic method to configure NixOS, which relies on data sources configured by `nix-channel` and has no version-locking mechanism, making it difficult to ensure the reproducibility of the system. **A better approach is to use Flakes**, which can ensure the reproducibility of the system and make it easy to manage the configuration.
Now first, let's learn how to manage NixOS through the classic method, `/etc/nixos/configuration.nix`, and then migrate to the more advanced Flakes.
### 1. Configuring the system using `/etc/nixos/configuration.nix`
As I mentioned earlier, this is the classic method to configured NixOS, and also the default method currently used by NixOS. It relies on data sources configured by `nix-channel` and has no version-locking mechanism, making it difficult to ensure the reproducibility of the system.
For example, to enable ssh and add a user "ryan", simply add the following content into `/etc/nixos/configuration.nix`:
```nix
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Omit the previous configuration.......
# add user ryan
users.users.ryan = {
isNormalUser = true;
description = "ryan";
extraGroups = [ "networkmanager" "wheel" ];
openssh.authorizedKeys.keys = [
# replace with your own public key
"ssh-ed25519 <some-public-key> ryan@ryan-pc"
];
packages = with pkgs; [
firefox
# thunderbird
];
};
# enable openssh-server
services.openssh = {
enable = true;
permitRootLogin = "no"; # disable root login
passwordAuthentication = false; # disable password login
openFirewall = true;
forwardX11 = true; # enable X11 forwarding
};
# omit the rest of the configuration.......
}
```
In this configuration, we declared that we want to enable the openssh service, add an ssh public key for the user ryan, and disable password login.
Now, let's run `sudo nixos-rebuild switch` to deploy the modified configuration, and then we can login to the system using ssh with the ssh keys we configured.
Any reproducible changes to the system can be made by modifying `/etc/nixos/configuration.nix` and deploying the changes by running `sudo nixos-rebuild switch`.
All configuration options in `/etc/nixos/configuration.nix` can be found in the following places:
- By searching on Google, such as `Chrome NixOS`, which will provide NixOS informations related to Chrome. Generally, the NixOS Wiki and the source code of Nixpkgs will be among the top results.
- By searching for keywords in [NixOS Options Search](https://search.nixos.org/options).
- For system-level configuration, relevant documentation can be found in [Configuration - NixOS Manual](https://nixos.org/manual/nixos/unstable/index.html#ch-configuration).
- By searching for keywords directly in the source code of [nixpkgs](https://github.com/NixOS/nixpkgs) on GitHub.
### 2. Enabling NixOS Flakes Support
Compared to the default configuration approach of NixOS, Flakes provide better reproducibility and a clearer package structure that is easier to maintain. Therefore, it is recommended to manage NixOS with Flakes.
However, as Flakes is still an experimental feature currently, it's not enabled by default yet, we need to enable it manually by modifying `/etc/nixos/configuration.nix`, example as follows:
```nix
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# omit the previous configuration.......
# enable Flakes and the new command line tool
nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [
# Flakes uses git to pull dependencies from data sources, so git must be installed first
git
vim
wget
curl
];
# omit the rest of the configuration.......
}
```
Now run `sudo nixos-rebuild switch` to apply the changes, and then you can write the configuration for NixOS with Flakes.
### 3. Switching System Configuration to `flake.nix`
After enabling `flakes`, `sudo nixos-rebuild switch` will try to read`/etc/nixos/flake.nix` first every time you run it, if not found, it will fallback to `/etc/nixos/configuration.nix`.
Now to learn how to write a flake, let's take a look at the official flake templates provided by Nix. First, check which templates are available:
```bash
nix flake show templates
```
The templates `templates#full` contains all possible usecases, let's take a look at it:
```bash
nix flake init -t templates#full
cat flake.nix
```
After reading this example, let's create a file `/etc/nixos/flake.nix` and copy the content of the example into it.
With `/etc/nixos/flake.nix`, all system modifications will be taken over by Flakes from now on.
The template we copied CAN NOT be used directly, we need to modify it to make it work, an example of `/etc/nixos/flake.nix` is as follows:
```nix
{
description = "Ryan's NixOS Flake";
# This is the standard format for flake.nix. `inputs` are the dependencies of the flake,
# and `outputs` function will return all the build results of the flake.
# Each item in `inputs` will be passed as a parameter to the `outputs` function after being pulled and built.
inputs = {
# There are many ways to reference flake inputs. The most widely used is github:owner/name/reference,
# which represents the GitHub repository URL + branch/commit-id/tag.
# Official NixOS package source, using nixos-unstable branch here
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# home-manager, used for managing user configuration
home-manager = {
url = "github:nix-community/home-manager/release-22.11";
# The `follows` keyword in inputs is used for inheritance.
# Here, `inputs.nixpkgs` of home-manager is kept consistent with the `inputs.nixpkgs` of the current flake,
# to avoid problems caused by different versions of nixpkgs.
inputs.nixpkgs.follows = "nixpkgs";
};
};
# `outputs` are all the build result of the flake.
# A flake can have many use cases and different types of outputs.
# parameters in `outputs` are defined in `inputs` and can be referenced by their names.
# However, `self` is an exception, This special parameter points to the `outputs` itself (self-reference)
# The `@` syntax here is used to alias the attribute set of the inputs's parameter, making it convenient to use inside the function.
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
# By default, NixOS will try to refer the nixosConfiguration with its hostname.
# so the system named `nixos-test` will use this configuration.
# However, the configuration name can also be specified using `sudo nixos-rebuild switch --flake /path/to/flakes/directory#<name>`.
# The `nixpkgs.lib.nixosSystem` function is used to build this configuration, the following attribute set is its parameter.
# Run `sudo nixos-rebuild switch --flake .#nixos-test` in the flake's directory to deploy this configuration on any NixOS system
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# The Nix module system can modularize configuration, improving the maintainability of configuration.
#
# Each parameter in the `modules` is a Nix Module, and there is a partial introduction to it in the nixpkgs manual:
# <https://nixos.org/manual/nixpkgs/unstable/#module-system-introduction>
# 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.
# If a Module is a function, this function can only have the following parameters:
#
# lib: the nixpkgs function library, which provides many useful functions for operating Nix expressions
# https://nixos.org/manual/nixpkgs/stable/#id-1.4
# config: all config options of the current flake
# options: all options defined in all NixOS Modules in the current flake
# pkgs: a collection of all packages defined in nixpkgs.
# you can assume its default value is `nixpkgs.legacyPackages."${system}"` for now.
# can be customed by `nixpkgs.pkgs` option
# modulesPath: the default path of nixpkgs's builtin modules folder,
# used to import some extra modules from nixpkgs.
# this parameter is rarely used, you can ignore it for now.
#
# Only these parameters can be passed by default.
# If you need to pass other parameters, you must use `specialArgs` by uncomment the following line
# specialArgs = {...} # pass custom arguments into sub module.
modules = [
# Import the configuration.nix we used before, so that the old configuration file can still take effect.
# Note: /etc/nixos/configuration.nix itself is also a Nix Module, so you can import it directly here
./configuration.nix
];
};
};
};
}
```
Here we defined a NixOS system called `nixos-test`, whose configuration file is `./configuration.nix`, which is the classic configuration we modified before, so we can still make use of it.
Now run `sudo nixos-rebuild switch` to apply the configuration, and no changes will be made to the system, because we imported the old configuration file in `/etc/nixos/flake.nix`, so the actual state we declared remains unchanged.
### 4. Manage system software through Flakes
After the switch, we can now manage the system through Flakes. The most common requirement for managing a system is to install softwares. We have seen how to install packages through `environment.systemPackages` before, and these packages are all from the official nixpkgs repository.
Now let's learn how to install packages from other sources through Flakes. This is much more flexible than installing from nixpkgs directly. The most obvious benefit is that you can easily set the version of the software.
Use [helix](https://github.com/helix-editor/helix) editor as an example, first we need to add the helix as an input into `flake.nix`:
```nix
{
description = "NixOS configuration of Ryan Yin";
# ......
inputs = {
# ......
# helix editor, use the tag 23.05
helix.url = "github:helix-editor/helix/23.05";
};
outputs = inputs@{ self, nixpkgs, ... }: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# set all inputs parameters as specialArgs of all sub module
# so that we can use `helix` input in sub modules
specialArgs = inputs;
modules = [
./configuration.nix
];
};
};
};
}
```
Then udpate `configuration.nix` to install `helix` from the input `helix`:
```nix
# Nix will automatically inject `helix` from specialArgs
# into the third parameter of this function through name matching
{ config, pkgs, helix, ... }:
{
# omit other configuration......
environment.systemPackages = with pkgs; [
git
vim
wget
curl
# install helix from the input `helix`
helix.packages."${pkgs.system}".helix
];
# omit other configuration......
}
```
Now deploy the changes by `sudo nixos-rebuild switch`, and then we can start the helix editor by `helix` command.
### 5. Add Custom Cache Mirror
> You can skip this section if you don't need to customize the cache mirror.
To speed up package building, Nix provides <https://cache.nixos.org> to cache build results to avoid build every packages locally.
With the NixOS's classic configuration method, other cache sources can be added by using `nix-channel`, but Flakes avoids using any system-level configuration and environment variables to ensure that its build results are not affected by the environment(so the build results are reproducible).
Therefore, to customize the cache source, we must add the related configuration in `flake.nix` by using the parameter `nixConfig`. An example is as follows:
```nix
{
description = "NixOS configuration of Ryan Yin";
# 1. To ensure purity, Flakes does not rely on the system's `/etc/nix/nix.conf`, so we have to set related configuration here.
# 2. To ensure security, flake allows only a few nixConfig parameters to be set directly by default.
# you need to add `--accept-flake-config` when executing the nix command,
# otherwise all other parameters will be ignored, and an warning will printed by nix.
nixConfig = {
experimental-features = [ "nix-command" "flakes" ];
substituters = [
# replace official cache with a mirror located in China
"https://mirrors.bfsu.edu.cn/nix-channels/store"
"https://cache.nixos.org/"
];
extra-substituters = [
# nix community's cache server
"https://nix-community.cachix.org"
];
extra-trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
inputs = {
# omit some configuration...
};
outputs = {
# omit some configuration...
};
}
```
After the modification, run `sudo nixos-rebuild switch` to apply the updates.
### 6. Install home-manager
As I mentioned earlier, NixOS can only manage system-level configuration, to manage the Home directory(user-level configuration), we need to install home-manager.
According to the official document [Home Manager Manual](https://nix-community.github.io/home-manager/index.htm), in order to install home-manager as a module of NixOS, we need to create `/etc/nixos/home.nix` first, an example content shown below:
```nix
{ config, pkgs, ... }:
{
# TODO please change the username & home direcotry to your own
home.username = "ryan";
home.homeDirectory = "/home/ryan";
# link the configuration file in current directory to the specified location in home directory
# home.file.".config/i3/wallpaper.jpg".source = ./wallpaper.jpg;
# link all files in `./scripts` to `~/.config/i3/scripts`
# home.file.".config/i3/scripts" = {
# source = ./scripts;
# recursive = true; # link recursively
# executable = true; # make all files executable
# };
# encode the file content in nix configuration file directly
# home.file.".xxx".text = ''
# xxx
# '';
# set cursor size and dpi for 4k monitor
xresources.properties = {
"Xcursor.size" = 16;
"Xft.dpi" = 172;
};
# basic configuration of git, please change to your own
programs.git = {
enable = true;
userName = "Ryan Yin";
userEmail = "xiaoyin_c@qq.com";
};
# Packages that should be installed to the user profile.
home.packages = [
# here is some command line tools I use frequently
# feel free to add your own or remove some of them
neofetch
nnn # terminal file manager
# archives
zip
xz
unzip
p7zip
# utils
ripgrep # recursively searches directories for a regex pattern
jq # A lightweight and flexible command-line JSON processor
yq-go # yaml processer https://github.com/mikefarah/yq
exa # A modern replacement for ls
fzf # A command-line fuzzy finder
# networking tools
mtr # A network diagnostic tool
iperf3
dnsutils # `dig` + `nslookup`
ldns # replacement of `dig`, it provide the command `drill`
aria2 # A lightweight multi-protocol & multi-source command-line download utility
socat # replacement of openbsd-netcat
nmap # A utility for network discovery and security auditing
ipcalc # it is a calculator for the IPv4/v6 addresses
# misc
cowsay
file
which
tree
gnused
gnutar
gawk
zstd
gnupg
# nix related
#
# it provides the command `nom` works just like `nix
# with more details log output
nix-output-monitor
# productivity
hugo # static site generator
glow # markdown previewer in terminal
btop # replacement of htop/nmon
iotop # io monitoring
iftop # network monitoring
# system call monitoring
strace # system call monitoring
ltrace # library call monitoring
lsof # list open files
# system tools
sysstat
lm_sensors # for `sensors` command
ethtool
pciutils # lspci
usbutils # lsusb
];
# starship - an customizable prompt for any shell
programs.starship = {
enable = true;
settings = {
add_newline = false;
aws.disabled = true;
gcloud.disabled = true;
line_break.disabled = true;
};
};
# alacritty - a cross-platform, GPU-accelerated terminal emulator
programs.alacritty = {
enable = true;
env.TERM = "xterm-256color";
font = {
size = 12;
draw_bold_text_with_bright_colors = true;
};
scrolling.multiplier = 5;
selection.save_to_clipboard = true;
};
programs.bash = {
enable = true;
enableCompletion = true;
bashrcExtra = ''
export PATH="$PATH:$HOME/bin:$HOME/.local/bin:$HOME/go/bin"
'';
# set some aliases, feel free to add more or remove some
shellAliases = {
urldecode = "python3 -c 'import sys, urllib.parse as ul; print(ul.unquote_plus(sys.stdin.read()))'";
urlencode = "python3 -c 'import sys, urllib.parse as ul; print(ul.quote_plus(sys.stdin.read()))'";
httpproxy = "export https_proxy=http://127.0.0.1:7890; export http_proxy=http://127.0.0.1:7890;";
};
};
# This value determines the home Manager release that your
# configuration is compatible with. This helps avoid breakage
# when a new home Manager release introduces backwards
# incompatible changes.
#
# You can update home Manager without changing this value. See
# the home Manager release notes for a list of state version
# changes in each release.
home.stateVersion = "22.11";
# Let home Manager install and manage itself.
programs.home-manager.enable = true;
}
```
After adding `/etc/nixos/home.nix`, you need to import this new configuration file in `/etc/nixos/flake.nix` to make use of it, use the following command to generate an example in the current folder for reference:
```shell
nix flake new example -t github:nix-community/home-manager#nixos
```
After adjusting the parameters, the content of `/etc/nixos/flake.nix` is as follows:
```nix
{
description = "NixOS configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ nixpkgs, home-manager, ... }: {
nixosConfigurations = {
# TODO please change the hostname to your own
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
# make home-manager as a module of nixos
# so that home-manager configuration will be deployed automatically when executing `nixos-rebuild switch`
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# TODO replace ryan with your own username
home-manager.users.ryan = import ./home.nix;
# Optionally, use home-manager.extraSpecialArgs to pass arguments to home.nix
}
];
};
};
};
}
```
Then run `sudo nixos-rebuild switch` to apply the configuration, and home-manager will be installed automatically.
After the installation, all user-level packages and configuration can be managed through `/etc/nixos/home.nix`. When running `sudo nixos-rebuild switch`, the configuration of home-manager will be applied automatically. (**It's not necessary to run `home-manager switch` manually**!)
To find the options we can use in `home.nix`, referring to the following documents:
- [Home Manager - Appendix A. Configuration Options](https://nix-community.github.io/home-manager/options.html): A list of all options, it is recommended to search for keywords in it.
- [home-manager](https://github.com/nix-community/home-manager): Some options are not listed in the official documentation, or the documentation is not clear enough, you can directly search and read the corresponding source code in this home-manager repo.
### 7. Modular NixOS configuration
At this point, the skeleton of the entire system is basically configured. The current configuration structure in `/etc/nixos` should be as follows:
```
$ tree
.
├── flake.lock
├── flake.nix
├── home.nix
└── configuration.nix
```
The functions of these four files are explained below:
- `flake.lock`: An automatically generated version-lock file, which records all input sources, hash values, and version numbers of the entire flake to ensure that the system is reproducible.
- `flake.nix`: The entry file, which will be recognized and deployed when executing `sudo nixos-rebuild switch`.
- See [Flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes) for all options of flake.nix.
- `configuration.nix`: Imported as a nix module in flake.nix, all system-level configuration are currently written here.
- See [Configuration - NixOS Manual](https://nixos.org/manual/nixos/unstable/index.html#ch-configuration) for all options of configuration.nix.
- `home.nix`: Imported by home-manager as the configuration of the user `ryan` in flake.nix, that is, it contains all the configuration of `ryan`, and is responsible for managing `ryan`'s home folder.
- See [Appendix A. Configuration Options - home Manager](https://nix-community.github.io/home-manager/options.html) for all options of home.nix.
By modifying these files, you can change the status of the system and the home directory declaratively.
As the configuration increases, it will be difficult to maintain the configuration by relying solely on `configuration.nix` and `home.nix`. Therefore, a better solution is to use the nix module system to split the configuration into multiple modules and write them in a classified manner.
nix module system provide a paramter, `imports`, which accept a list of `.nix` files, and merge all the configuration defined in these files into the current nix module. Note that the word used here is "**merge**", which means that `imports` will **NOT** simply overwrite the duplicate configuration, but handle them more reasonably. For example, if I define `program.packages = [...]` in multiple modules, then `imports` will merge all `program.packages` defined in all nix modules into one list. Not only lists can be merged correctly, but attribute sets can also be merged correctly. The specific behavior can be explored by yourself.
> I only found a description of `imports` in [nixpkgs-unstable official manual - evalModules parameters](https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules-parameters): `A list of modules. These are merged together to form the final configuration.`, it's a bit ambiguous...
With the help of `imports`, we can split `home.nix` and `configuration.nix` into multiple nix modules defined in diffrent `.nix` files.
Use [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2) as an example, which is the configuration of my previous NixOS system with i3 window manager. The structure of it is as follows:
```shell
├── flake.lock
├── flake.nix
├── home
│ ├── default.nix # here we import all submodules by imports = [...]
│ ├── fcitx5 # fcitx5 input method's configuration
│ │ ├── default.nix
│ │ └── rime-data-flypy
│ ├── i3 # i3 window manager's configuration
│ │ ├── config
│ │ ├── default.nix
│ │ ├── i3blocks.conf
│ │ ├── keybindings
│ │ └── scripts
│ ├── programs
│ │ ├── browsers.nix
│ │ ├── common.nix
│ │ ├── default.nix # here we import all modules in programs folder by imports = [...]
│ │ ├── git.nix
│ │ ├── media.nix
│ │ ├── vscode.nix
│ │ └── xdg.nix
│ ├── rofi # rofi launcher's configuration
│ │ ├── configs
│ │ │ ├── arc_dark_colors.rasi
│ │ │ ├── arc_dark_transparent_colors.rasi
│ │ │ ├── power-profiles.rasi
│ │ │ ├── powermenu.rasi
│ │ │ ├── rofidmenu.rasi
│ │ │ └── rofikeyhint.rasi
│ │ └── default.nix
│ └── shell # shell/terminal related configuration
│ ├── common.nix
│ ├── default.nix
│ ├── nushell
│ │ ├── config.nu
│ │ ├── default.nix
│ │ └── env.nu
│ ├── starship.nix
│ └── terminals.nix
├── hosts
│ ├── msi-rtx4090 # My main machine's configuration
│ │ ├── default.nix # This is the old configuration.nix, but most of the content has been split out to modules.
│ │ └── hardware-configuration.nix # hardware & disk related configuration, autogenerated by nixos
│ └── nixos-test # my test machine's configuration
│ ├── default.nix
│ └── hardware-configuration.nix
├── modules # some common NixOS modules that can be reused
│ ├── i3.nix
│ └── system.nix
└── wallpaper.jpg # wallpaper
```
For more details, see [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2).
#### 7.1. `mkOverride`, `lib.mkDefault` and `lib.mkForce`
You may found some people use `lib.mkDefault` `lib.mkForce` to define values in Nix files, as their names suggest, `lib.mkDefault` and `lib.mkForce` are used to set default values or force values of options.
You can read the source code of `lib.mkDefault` and `lib.mkForce` to understand them by running `nix repl -f '<nixpkgs>'` and then enter `:e lib.mkDefault`(To learn the basic usage of `nix repl`, just type `:?` to see the help information).
its source code is as follows:
```nix
# ......
mkOverride = priority: content:
{ _type = "override";
inherit priority content;
};
mkOptionDefault = mkOverride 1500; # priority of option defaults
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
mkForce = mkOverride 50;
mkVMOverride = mkOverride 10; # used by nixos-rebuild build-vm
# ......
```
So `lib.mkDefault` is used to set default values of options, it has a priority of 1000 internally,
and `lib.mkForce` is used to force values of options, it has a priority of 50 internally.
If you just set a value of an option directly, it will be set with a default priority of 1000(the same as `lib.mkDefault`).
the lower the `priority`'s value is, the higher the actual priority is, so `lib.mkForce` has a higher priority than `lib.mkDefault`.
If you defined multiple values with the same priority, Nix will throw an error.
They are useful to modularize the configuration, as you can set default values in a low-level module(base module), and force values in a high-level module.
For example, I defined some default values in <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-server.nix#L30>:
```nix
{ lib, pkgs, ... }:
{
# ......
nixpkgs.config.allowUnfree = lib.mkDefault false;
# ......
}
```
And for my dekstop machine, I force the values to another value in <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-desktop.nix#L15>:
```nix
{ lib, pkgs, ... }:
{
# import the base module
imports = [
./core-server.nix
];
# override the default value defined in the base module
nixpkgs.config.allowUnfree = lib.mkForce true;
# ......
}
```
#### 7.2 `lib.mkOrder`, `lib.mkBefore` and `lib.mkAfter`
`lib.mkBefore` and `lib.mkAfter` are used to set the merge order of **list-type options**, just like `lib.mkDefault` and `lib.mkForce`, they're also useful to modularize the configuration.
I said before that if you defined multiple values with the same **override priority**, Nix will throw an error.
But with `lib.mkOrder`, `lib.mkBefore` or `lib.mkAfter`, you can define multiple values with the same override priority, they will be merged in the order you defined.
Let's running `nix repl -f '<nixpkgs>'` and then enter `:e lib.mkBefore` to take a look at its source code(To learn the basic usage of `nix repl`, just type `:?` to see the help information):
```nix
# ......
mkOrder = priority: content:
{ _type = "order";
inherit priority content;
};
mkBefore = mkOrder 500;
mkAfter = mkOrder 1500;
# The default priority for things that don't have a priority specified.
defaultPriority = 100;
# ......
```
So `lib.mkBefore` is a shortcut for `lib.mkOrder 500`, and `lib.mkAfter` is a shortcut for `lib.mkOrder 1500`.
To test the usage of `lib.mkBefore` and `lib.mkAfter`, let's create a simple Flake project:
```shell
# create flake.nix with the following content
cat <<EOF | sudo tee flake.nix
{
description = "Ryan's NixOS Flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# demo module 1, insert git at the head of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkBefore [pkgs.git];
})
# demo module 2, insert vim at the tail of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkAfter [pkgs.vim];
})
# demo module 3, just add curl to the list normally
({lib, pkgs, ...}: {
environment.systemPackages = with pkgs; [curl];
})
];
};
};
};
}
EOF
# create flake.lock
nix flake update
# enter nix repl environment
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# load the flake we just created
nix-repl> :lf .
Added 9 variables.
# check the order of systemPackages
nix-repl> outputs.nixosConfigurations.nixos-test.config.environment.systemPackages
[ «derivation /nix/store/0xvn7ssrwa0ax646gl4hwn8cpi05zl9j-git-2.40.1.drv»
«derivation /nix/store/7x8qmbvfai68sf73zq9szs5q78mc0kny-curl-8.1.1.drv»
«derivation /nix/store/bly81l03kh0dfly9ix2ysps6kyn1hrjl-nixos-container.drv»
......
......
«derivation /nix/store/qpmpvq5azka70lvamsca4g4sf55j8994-vim-9.0.1441.drv» ]
```
So we can see that the order of `systemPackages` is `git -> curl -> default packages -> vim`, which is the same as the order we defined in `flake.nix`.
> Though it's useless to adjust the order of `systemPackages`, it may be helpful at some other places...
### 8. Update the system
With Flakes, it is also very simple to update the system. Just run the following commands in `/etc/nixos`:
```shell
# update flake.lock
nix flake update
# apply the updates
sudo nixos-rebuild switch
```
Sometimes you may encounter some error of sha256 mismatch when running `nixos-rebuild switch`, which may be solved by updating `flake.lock` through `nix flake update`.
### 9. Downgrade or upgrade some packages
After using Flakes, most people are currently using the `nixos-unstable` branch of nixpkgs. Sometimes you will encounter some bugs, such as the [chrome/vscode crash problem](https://github.com/swaywm/sway/issues/7562)
To resolve problems, we may need to downgrade or upgrade some packages. In Flakes, all package versions and hash values are one-to-one corresponding to the git commit of their flake input.
Therefore, to downgrade or upgrade a package, we need to lock the git commit of its flake input.
For exmaple, let's add multiple nixpkgs, each using a different git commit or branch:
```nix
{
description = "NixOS configuration of Ryan Yin"
inputs = {
# default to nixos-unstable branch
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
# the latest stable branch of nixpkgs, used to rollback the version of some packages
# the current latest version is 22.11
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-22.11";
# we can also use git commit hash to lock the version
nixpkgs-fd40cef8d.url = "github:nixos/nixpkgs/fd40cef8d797670e203a27a91e4b8e6decf0b90c";
outputs = inputs@{
self,
nixpkgs,
nixpkgs-stable,
nixpkgs-fd40cef8d,
...
}: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
# The core parameter, which passes the non-default nixpkgs instances to other nix modules
specialArgs = {
# To use packages from nixpkgs-stable, we need to configure some parameters for it first
pkgs-stable = import nixpkgs-stable {
system = system; # refer the `system` parameter form outer scope recursively
# To use chrome, we need to allow the installation of non-free software
config.allowUnfree = true;
};
pkgs-fd40cef8d = import nixpkgs-fd40cef8d {
system = system;
config.allowUnfree = true;
};
};
modules = [
./hosts/nixos-test
# omit other configuration...
];
};
};
};
}
```
And then refer the packages from `pkgs-stable` or `pkgs-fd40cef8d` in your sub module, a home manager's sub module as an example:
```nix
{
pkgs,
config,
# nix will search and jnject this parameter from specialArgs in flake.nix
pkgs-stable,
# pkgs-fd40cef8d,
...
}:
{
# refer packages from pkgs-stable instead of pkgs
home.packages = with pkgs-stable; [
firefox-wayland
# chrome wayland support was broken on nixos-unstable branch, so fallback to stable branch for now
# https://github.com/swaywm/sway/issues/7562
google-chrome
];
programs.vscode = {
enable = true;
package = pkgs-stable.vscode; # refer vscode from pkgs-stable instead of pkgs
};
}
```
After adjusted the configuration, deploy it with `sudo nixos-rebuild switch`, then your firefox/chrome/vscode will be downgraded to the version corresponding to `nixpkgs-stable` or `nixpkgs-fd40cef8d`.
> according to [1000 instances of nixpkgs](https://discourse.nixos.org/t/1000-instances-of-nixpkgs/17347), it's not a good practice to use `import` in sub modules to customize `nixpkgs`, because each `import` will create a new instance of nixpkgs, which will increase the build time and memory usage as the configuration grows. So here we create all nixpkgs instances in `flake.nix` to avoid this problem.
### 10. Manage the configuration with Git
NixOS configuration is just a set of text files, it is very suitable to be managed with Git, and thus we can easily rollback to a previous version when we encounter some problems.
However, NixOS places the configuration in `/etc/nixos` by default, which requires root permissions to modify, which is not convenient for daily use.
Luckily, Flakes can solve this problem, you can place your flake anywhere you like.
For example, my usage is to place my flake in `~/nixos-config`, and then create a soft link in `/etc/nixos`:
```shell
sudo mv /etc/nixos /etc/nixos.bak # backup the original configuration
sudo ln -s ~/nixos-config/ /etc/nixos
# deploy the flake.nix located at the default location(/etc/nixos)
sudo nixos-rebuild switch
```
And then you can use Git to manage the configuration in `~/nixos-config`. The configuration can be used with ordinary user-level permissions, and it is not required to be owned by root.
Another method is jsut to delete `/etc/nixos` directly, and specify the configuration file path each time you deploy it:
```shell
sudo mv /etc/nixos /etc/nixos.bak
cd ~/nixos-config
# `--flake .#nixos-test` means deploy the flake.nix located in the current directory, and the nixosConfiguration's name is `nixos-test`
sudo nixos-rebuild switch --flake .#nixos-test
```
Choose whichever you like. After that, system rollback will become very simple, just switch to the previous commit and then deploy it:
```shell
cd ~/nixos-config
# switch to the previous commit
git checkout HEAD^1
# deploy the flake.nix located in the current directory, and the nixosConfiguration's name is `nixos-test`
sudo nixos-rebuild switch --flake .#nixos-test
```
More operations on Git are not described here. Generally speaking, rollback can be done directly through Git. Only when the system crashes completely, you will need to restart into bootloader and boot the system from the previous historical version.
### 11. View and delete history data {#view-and-delete-history}
As we mentioned before, each deployment of NixOS will generate a new version, and all versions will be added to the system boot options. In addition to restarting the computer, we can also query all available historical versions through the following command:
```shell
nix profile history --profile /nix/var/nix/profiles/system
```
The command to clean up historical versions to release storage space:
```shell
# delete all historical versions older than 7 days
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d
# we need to collect garbages after wipe-history
sudo nix store gc --debug
```
Another command returns all packages installed in the system:
```shell
nix-env -qa
```

309
docs/nixpkgs/index.md Normal file
View file

@ -0,0 +1,309 @@
## VIII. Nixpkgs's Advanced Usage
`callPackage`, `Overriding`, and `Overlays` are the techniques occasionally used when using Nix to customize the build method of Nix packages.
We know that many programs have a large number of build parameters that need to be configured, and different users may want to use different build parameters. This is where `Overriding` and `Overlays` come in handy. Let me give you a few examples I have encountered:
1. [`fcitx5-rime.nix`](https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix): By default, `fcitx5-rime` use `rime-data` as the value of `rimeDataPkgs`, but this parameter can be customized by `override`.
2. [`vscode/with-extensions.nix`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/editors/vscode/with-extensions.nix): This package for VS Code can also be customized by overriding the value of `vscodeExtensions`, thus we can install some custom plugins into VS Code.
- [`nix-vscode-extensions`](https://github.com/nix-community/nix-vscode-extensions): This is a vscode plugin manager implemented by overriding `vscodeExtensions`.
3. [`firefox/common.nix`](https://github.com/NixOS/nixpkgs/blob/416ffcd08f1f16211130cd9571f74322e98ecef6/pkgs/applications/networking/browsers/firefox/common.nix): Firefox has many customizable parameters too.
4. ...
In short, `Overriding` or `Overlays` can be used to customize the build parameters of Nix packages.
### 1. pkgs.callPackage {#callpackage}
> [Chapter 13. Callpackage Design Pattern - Nix Pills](https://nixos.org/guides/nix-pills/callpackage-design-pattern.html)
In the previous content, We have used `import xxx.nix` to import Nix files many times, this syntax simply returns the execution result of the file, without any further processing of the it.
`pkgs.callPackage` is also used to import Nix files, its syntax is `pkgs.callPackage xxx.nix { ... }`, but unlike `import`, the Nix file imported by it must be a Derivation or a function that returns a Derivation. Its result is a Derivation(a software package) too.
So what does the Nix file that can be used as a parameter of `pkgs.callPackge` look like? You can take a look at the `hello.nix` `fcitx5-rime.nix` `vscode/with-extensions.nix` `firefox/common.nix` we mentioned earlier, they can all be imported by `pkgs.callPackage`.
When the `xxx.nix` used in `pkgs.callPackge xxx.nix {...}` is a function (most Nix packages are like this), the execution flow is as follows:
1. `pkgs.callPackge xxx.nix {...}` will first `import xxx.nix` to get the function defined in it. The parameters of this function usually have `lib`, `stdenv`, `fetchurl` and other parameters, as well as some custom parameters, which usually have default values.
2. Then `pkgs.callPackge` will first look up the value matching the name from the current environment as the parameter to be passed to the function. parameters like `lib` `stdenv` `fetchurl` are defined in nixpkgs, and they will be found in this step.
3. Then `pkgs.callPackge` will merge its second parameter `{...}` with the attribute set obtained in the previous step, and then pass it to the function imported from `xxx.nix` and execute it.
4. Finally we get a Derivation as the result of the function execution.
So the common usage of `pkgs.callPackage` is to import custom Nix packages and used it in Nix Module.
For example, we wrote a `hello.nix` ourselves, and then we can use `pkgs.callPackage ./hello.nix {}` in any Nix Module to import and use it.
### 2. Overriding {#overriding}
> [Chapter 4. Overriding - nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#chap-overrides)
Simply put, all Nix packages in nixpkgs can be customized with `<pkg>.override {}` to define some build parameters, which returns a new Derivation that uses custom parameters. For example:
```nix
pkgs.fcitx5-rime.override {rimeDataPkgs = [
./rime-data-flypy
];}
```
The result of this Nix expression is a new Derivation, where `rimeDataPkgs` is overridden as `[./rime-data-flypy]`, while other parameters remain their original values.
How to know which parameters of `fcitx5-rime` can be overridden? There are several ways:
1. Try to find the source code of the package in the nixpkgs repository on GitHub, such as [fcitx5-rime.nix](https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix)
1. Note: Be sure to select the correct branch, for example, if you are using the nixos-unstable branch, you need to find it in the nixos-unstable branch.
2. Check by using `nix repl '<nixpkgs>'`, then enter `:e pkgs.fcitx5-rime`, which will open the source code of this package through the default editor, and then you can see all the parameters of this package.
1. Note: To learn the basic usage of `nix repl`, just type `:?` to see the help information
Through these two methods, you can see that the `fcitx5-rime` package has the following input parameters, which can all be modified by `override`:
```nix
{ lib, stdenv
, fetchFromGitHub
, pkg-config
, cmake
, extra-cmake-modules
, gettext
, fcitx5
, librime
, rime-data
, symlinkJoin
, rimeDataPkgs ? [ rime-data ]
}:
stdenv.mkDerivation rec {
...
}
```
Instead of override the function's parameters, we can also override the attributes of the Derivation created by `stdenv.mkDerivation`.
Take `pkgs.hello` as an example, first check the source code of this package through the method we mentioned earlier:
```nix
# https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/applications/misc/hello/default.nix
{ callPackage
, lib
, stdenv
, fetchurl
, nixos
, testers
, hello
}:
stdenv.mkDerivation (finalAttrs: {
pname = "hello";
version = "2.12.1";
src = fetchurl {
url = "mirror://gnu/hello/hello-${finalAttrs.version}.tar.gz";
sha256 = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA=";
};
doCheck = true;
# ......
})
```
The attributes showed above, such as `pname` `version` `src` `doCheck`, can all be overridden by `overrideAttrs`, for example:
```nix
helloWithDebug = pkgs.hello.overrideAttrs (finalAttrs: previousAttrs: {
doCheck = false;
});
```
Here we use `overrideAttrs` to override `doCheck`, while other attributes remain their original values.
Some default attributes defined in `stdenv.mkDerivation` can also be overridden by `overrideAttrs`, for example:
```nix
helloWithDebug = pkgs.hello.overrideAttrs (finalAttrs: previousAttrs: {
separateDebugInfo = true;
});
```
The attribute we override here, `separateDebugInfo`, is defined in `stdenv.mkDerivation`, not in the source code of `hello`.
We can check the source code of `stdenv.mkDerivation` to see all the attributes defined in it by using `nix repl '<nixpkgs>'` and then enter `:e stdenv.mkDerivation`(To learn the basic usage of `nix repl`, just type `:?` to see the help information).
### 3. Overlays
> [Chapter 3. Overlays - nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#chap-overlays)
The `override` we introduced previously will generate a new Derivation, which does not affect the original Derivation in `pkgs`, and is only suitable for use as a local parameter,
if you need to override a Derivation that is also depended on by other Nix packages, then other Nix packages will still use the original Derivation.
To solve this problem, Nix provides the ability to use `overlays`. Simply put, `overlays` can globally modify the Derivation in `pkgs`.
In the classic Nix environment, Nix automatically applies all `overlays` configuration under the paths `~/.config/nixpkgs/overlays.nix` `~/.config/nixpkgs/overlays/*.nix`,
but in Flakes, in order to ensure the reproducibility of the system, it cannot depend on any configuration outside the Git repository, so this classic method cannot be used now.
When using Flakes to write configuration for NixOS, home Manager and NixOS both provide the `nixpkgs.overlays` option to define `overlays`, related documentation:
- [home-manager docs - `nixpkgs.overlays`](https://nix-community.github.io/home-manager/options.html#opt-nixpkgs.overlays)
- [nixpkgs source code - `nixpkgs.overlays`](https://github.com/NixOS/nixpkgs/blob/30d7dd7e7f2cba9c105a6906ae2c9ed419e02f17/nixos/modules/misc/nixpkgs.nix#L169)
For example, the following content is a Module that loads Overlays, which can be used as either a home Manager Module or a NixOS Module, because the two definitions are exactly the same:
> home Manager is an external component after all, and most people use the unstable branch of home Manager & nixpkgs, which sometimes causes problems with home Manager Module, so it is recommended to import `overlays` in a NixOS Module.
```nix
{ config, pkgs, lib, ... }:
{
nixpkgs.overlays = [
# overlayer1 - use self and super to express the inheritance relationship
(self: super: {
google-chrome = super.google-chrome.override {
commandLineArgs =
"--proxy-server='https=127.0.0.1:3128;http=127.0.0.1:3128'";
};
})
# overlayer2 - you can also use `extend` to inherit other overlays
# use `final` and `prev` to express the relationship between the new and the old
(final: prev: {
steam = prev.steam.override {
extraPkgs = pkgs:
with pkgs; [
keyutils
libkrb5
libpng
libpulseaudio
libvorbis
stdenv.cc.cc.lib
xorg.libXcursor
xorg.libXi
xorg.libXinerama
xorg.libXScrnSaver
];
extraProfile = "export GDK_SCALE=2";
};
})
# overlay3 - define overlays in other files
# here the content of overlay3.nix is the same as above:
# `final: prev: { xxx = prev.xxx.override { ... }; }`
(import ./overlays/overlay3.nix)
];
}
```
refer to this example to write your own overlays, import the configuration as a NixOS Module or a home Manager Module, and then deploy it to see the effect.
#### Modular overlays
The previous example shows how to write overlays, but all overlays are written in a single nix file, which is a bit difficult to maintain.
To resolve this problem,here is a best practice of how to manage overlays in a modular way.
First, create an `overlays` folder in the Git repository to store all overlays configuration, and then create `overlays/default.nix`, whose content is as follows:
```nix
args:
# import all nix files in the current folder, and execute them with args as parameters
# The return value is a list of all execution results, which is the list of overlays
builtins.map
(f: (import (./. + "/${f}") args)) # the first parameter of map, a function that import and execute a nix file
(builtins.filter # the second parameter of map, a list of all nix files in the current folder except default.nix
(f: f != "default.nix")
(builtins.attrNames (builtins.readDir ./.)))
```
Then you can write all overlays configuration in the `overlays` folder, an example configuration `overlays/fcitx5/default.nix` is as follows:
```nix
# to add my custom input method, I override the default rime-data here
# refer to https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix
{pkgs, config, lib, ...}:
(self: super: {
# my custom input method's rime-data, downloaded from https://flypy.com
rime-data = ./rime-data-flypy;
fcitx5-rime = super.fcitx5-rime.override { rimeDataPkgs = [ ./rime-data-flypy ]; };
})
```
I custom the `rime-data` package through the overlay shown above.
At last, you need to load all overlays returned by `overlays/default.nix` through the `nixpkgs.overlays` option, add the following parameter to any NixOS Module to achieve this:
```nix
{ config, pkgs, lib, ... } @ args:
{
# ......
# add this parameter
nixpkgs.overlays = import /path/to/overlays/dir;
# ......
}
```
For example, you can just add it directly in `flake.nix`:
```nix
{
description = "NixOS configuration of Ryan Yin";
# ......
inputs = {
# ......
};
outputs = inputs@{ self, nixpkgs, ... }: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = inputs;
modules = [
./hosts/nixos-test
# add the following inline module definition
# here, all parameters of modules are passed to overlays
(args: { nixpkgs.overlays = import ./overlays args; })
# ......
];
};
};
};
}
```
By using this modular approach, it is very convenient to modularize all your overlays. Taking my configuration as an example, the structure of the `overlays` folder is roughly as follows:
```nix
.
├── flake.lock
├── flake.nix
├── home
├── hosts
├── modules
├── ......
├── overlays
│ ├── default.nix # it returns a list of all overlays.
│ └── fcitx5 # fcitx5 overlay
│ ├── default.nix
│ ├── README.md
│ └── rime-data-flypy # my custom rime-data
│ └── share
│ └── rime-data
│ ├── ...... # rime-data files
└── README.md
```
## IV. Package Repositories of Nix
Similar to Arch Linux, Nix also has official and community software package repositories:
1. [nixpkgs](https://github.com/NixOS/nixpkgs) is a Git repository containing all Nix packages and NixOS modules.
1. Its `master` branch contains the latest Nix packages and modules.
2. The `nixos-unstable` branch contains the latest tested modules, but some bugs may still exist.
3. And the `nixos-XX.YY` branch(the stable branch) contains the latest stable Nix packages and modules.
2. [NUR](https://github.com/nix-community/NUR) is similar to Arch Linux's AUR.
1. NUR is a third-party Nix package repository and serves as a supplement to nixpkgs, use it at your own risk.
3. Flakes can also install software packages directly from Git repositories, which can be used to install Flakes provided by anyone, we will talk about this later.

View file

@ -0,0 +1,8 @@
## V. Basics of The Nix language
The Nix language is used to declare the configuration to be built by Nix, if you want to play with NixOS and Flakes and enjoy the benefits they bring, you must learn the basics of this language first.
The Nix language is a simple functional language, if you already have some experience in programming, it should take less than 2 hours to go through its basics.
Please read [**Nix language basics - nix.dev**](https://nix.dev/tutorials/first-steps/nix-language) and [Chapter 4. The Basics of the Language - Nix Pills](https://nixos.org/guides/nix-pills/basics-of-language.html) to get a basic understanding of the Nix language now, they are all good introductory materials.

View file

@ -0,0 +1,24 @@
After becoming familiar with the NixOS, you can further explore Nix's three manuals and other docs to discover more ways to use it:
- [Nix Reference Manual](https://nixos.org/manual/nix/stable/package-management/profiles.html): A guide to the Nix package manager, which mainly covers the design of the package manager and instructions for using it from the command line.
- [nixpkgs Manual](https://nixos.org/manual/nixpkgs/unstable/): A manual that introduces parameters of Nixpkgs, how to use, modify, and package Nix packages.
- [NixOS Manual](https://nixos.org/manual/nixos/unstable/): A user manual for NixOS, mainly including configuration instructions for system-level components such as Wayland/X11 and GPU.
- [nix-pills](https://nixos.org/guides/nix-pills): Nix Pills provides an in-depth explanation of how to use Nix to build software packages. It is written in a clear and understandable way and is worth reading, as it is also sufficiently in-depth.
After becoming familiar with Flakes, you may want to try some advanced techniques. Here are some popular community projects to try:
- [flake-parts](https://github.com/hercules-ci/flake-parts): Simplify the writing and maintenance of configuration through the Module module system.
- [flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus): A third-party package for simplifying Flake configuration, which is apparently more powerful.
- [digga][digga]: A large and comprehensive Flake template that combines the functionality of various useful Nix toolkits, but has a complex structure and requires some experience to navigate.
- etc.
And many other useful community projects to explore, here are some of them:
- [dev-templates](https://github.com/the-nix-way/dev-templates): Dev environments for numerous languages based on Nix flakes.
- [devenv](https://github.com/cachix/devenv): development environment management
- [agenix](https://github.com/ryantm/agenix): secrets management
- [colmena](https://github.com/zhaofengli/colmena): NixOS deployment tools
- [nixos-generator](https://github.com/nix-community/nixos-generators): generate iso/qcow2/... from nixos configuration
- [lanzaboote](https://github.com/nix-community/lanzaboote): enable secure boot for NixOS
- [impermanence](https://github.com/nix-community/impermanence): used to make NixOS stateless, to imporve the reproduciability of NixOS system.

View file

@ -0,0 +1,351 @@
Nix is powerful and flexible, it provides a lot of ways to do things, making it difficult to find the most suitable way to do your job.
Here are some best practices that I've learned from the community, hope it can help you.
## 1. Run downloaded binaries on NixOS
NixOS does not follow the FHS standard, so the binaries you download from the Internet will not likely work on NixOS. But there are some ways to make it work.
Here is a detailed guide which provides 10 ways to run downloaded binaries on NixOS: [Different methods to run a non-nixos executable on Nixos](https://unix.stackexchange.com/questions/522822/different-methods-to-run-a-non-nixos-executable-on-nixos), I recommend you to read it.
Among these methods, I prefer creating a FHS environment to run the binary, which is very convenient and easy to use.
To create such an environment, add the following code to one of your nix modules:
```nix
{ config, pkgs, lib, ... }:
{
# ......omit many configurations
environment.systemPackages = with pkgs; [
# ......omit many packages
# create a fhs environment by command `fhs`, so we can run non-nixos packages in nixos!
(pkgs.buildFHSUserEnv (base // {
name = "fhs";
targetPkgs = pkgs: (
# pkgs.buildFHSUserEnv provides only a minimal fhs environment,
# it lacks many basic packages needed by most softwares.
# so we need to add them manually.
#
# pkgs.appimageTools provides basic packages needed by most softwares.
(pkgs.appimageTools.defaultFhsEnvArgs.targetPkgs pkgs) ++ with pkgs; [
pkg-config
ncurses
# feel free to add more packages here, if you need
]
);
profile = "export FHS=1";
runScript = "bash";
extraOutputsToInstall = ["dev"];
}))
];
# ......omit many configurations
}
```
after applying the updated configuration, you can run `fhs` to enter the FHS environment, and then run the binary you downloaded, e.g.
```shell
# Activating FHS drops me in a shell which looks like a "normal" Linux
$ fhs
# check what we have in /usr/bin
(fhs) $ ls /usr/bin
# try to run a non-nixos binary downloaded from the Internet
(fhs) $ ./bin/code
```
## 2. check the source code and debug with `nix repl`
We've used `nix repl '<nixpkgs>'` many times to check the source code in this guide, it's really a powerful tool to help us understand how things work in Nix.
Better take a look at the help message of `nix repl`:
```
nix repl -f '<nixpkgs>'
Welcome to Nix 2.13.3. Type :? for help.
Loading installable ''...
Added 17755 variables.
nix-repl> :?
The following commands are available:
<expr> Evaluate and print expression
<x> = <expr> Bind expression to variable
:a <expr> Add attributes from resulting set to scope
:b <expr> Build a derivation
:bl <expr> Build a derivation, creating GC roots in the working directory
:e <expr> Open package or function in $EDITOR
:i <expr> Build derivation, then install result into current profile
:l <path> Load Nix expression and add it to scope
:lf <ref> Load Nix flake and add it to scope
:p <expr> Evaluate and print expression recursively
:q Exit nix-repl
:r Reload all files
:sh <expr> Build dependencies of derivation, then start nix-shell
:t <expr> Describe result of evaluation
:u <expr> Build derivation, then start nix-shell
:doc <expr> Show documentation of a builtin function
:log <expr> Show logs for a derivation
:te [bool] Enable, disable or toggle showing traces for errors
```
Some expressions that I use frequently: `:lf <ref>`, `:e <expr>`.
`:e <expr>` is very intuitive, so I won't repeat it. let's take a look at `:lf <ref>`:
```nix
# cd into my nix-config repo(you should replace it with your own nix-config repo)
cd ~/nix-config/
# enter nix repl
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# load my nix flake and add it to scope
nix-repl> :lf .
Added 16 variables.
# press <TAB> to see what we have in scope
nix-repl><TAB>
# ......omit some outputs
__isInt nixosConfigurations
__isList null
__isPath outPath
__isString outputs
__langVersion packages
# ......omit some outputs
# check what's in inputs
nix-repl> inputs.<TAB>
inputs.agenix inputs.nixpkgs
inputs.darwin inputs.nixpkgs-darwin
inputs.home-manager inputs.nixpkgs-unstable
inputs.hyprland inputs.nixpkgs-wayland
inputs.nil
inputs.nixos-generators
# check what's in inputs.nil
nix-repl> inputs.nil.packages.
inputs.nil.packages.aarch64-darwin
inputs.nil.packages.aarch64-linux
inputs.nil.packages.x86_64-darwin
inputs.nil.packages.x86_64-linux
# check the outputs of my nix flake
nix-repl> outputs.nixosConfigurations.<TAB>
outputs.nixosConfigurations.ai
outputs.nixosConfigurations.aquamarine
outputs.nixosConfigurations.kana
outputs.nixosConfigurations.ruby
nix-repl> outputs.nixosConfigurations.ai.<TAB>
outputs.nixosConfigurations.ai._module
outputs.nixosConfigurations.ai._type
outputs.nixosConfigurations.ai.class
outputs.nixosConfigurations.ai.config
outputs.nixosConfigurations.ai.extendModules
outputs.nixosConfigurations.ai.extraArgs
outputs.nixosConfigurations.ai.options
outputs.nixosConfigurations.ai.pkgs
outputs.nixosConfigurations.ai.type
nix-repl> outputs.nixosConfigurations.ai.config.
outputs.nixosConfigurations.ai.config.age
outputs.nixosConfigurations.ai.config.appstream
outputs.nixosConfigurations.ai.config.assertions
outputs.nixosConfigurations.ai.config.boot
outputs.nixosConfigurations.ai.config.console
outputs.nixosConfigurations.ai.config.containers
# ......omit other outputs
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.activation
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.activationPackage
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.emptyActivationPath
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.enableDebugInfo
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.enableNixpkgsReleaseCheck
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraActivationPath
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraBuilderCommands
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraOutputsToInstall
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.extraProfileCommands
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file
# ......omit other outputs
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.BROWSER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.DELTA_PAGER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.EDITOR
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.GLFW_IM_MODULE
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.MANPAGER
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.QT_IM_MODULE
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.TERM
# ......omit other outputs
# check the value of `TERM`
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.TERM
"xterm-256color"
# check all files defined by `home.file`
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file.<TAB>
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..bash_profile
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..bashrc
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/fcitx5/profile
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/fcitx5/profile-bak
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/config
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/i3blocks.conf
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/keybindings
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/layouts
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/scripts
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/i3/wallpaper.png
outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.file..config/rofi
#......
```
As you can see, we can check every attribute of my flake in the REPL after loading it, which is very convenient for debugging.
## 3. Remote deployment
Some tools like [NixOps](https://github.com/NixOS/nixops), [deploy-rs](https://github.com/serokell/deploy-rs), and [colmena](https://github.com/zhaofengli/colmena) can all be used to deploy NixOS configuration to remote hosts, but they are all too complicated for me, so skip them all.
`nixos-rebuild`, the tool we use to deploy NixOS configuration, also supports remote deployment through ssh protocol, which is very convenient and simple.
But `nixos-rebuild` does not support deploying with password authentication, so to use it for remote deployment, we need to:
1. Configure ssh public key authentication for the remote hosts.
2. To avoid sudo password verification failures, we need to use the `root` user to deploy, or grant the user sudo permission without password verification.
1. related issue: <https://github.com/NixOS/nixpkgs/issues/118655>
After the above configuration is completed, we can deploy the configuration to the server through the following command:
```bash
# 1. add the ssh key to ssh-agent first
ssh-add ~/.ssh/ai-idols
# 2. deploy the configuration to the remote host, using the ssh key we added in step 1
# and the username defaults to `$USER`, it's `ryan` in my case.
nixos-rebuild --flake .#aquamarine --target-host 192.168.4.1 --build-host 192.168.4.1 switch --use-remote-sudo --verbose
```
The commands above will build & deploy the configuration to aquamarine, the build process will be executed on aquamarine too,
and the `--use-remote-sudo` option indicates that we need to use sudo permission on the remote server to deploy the configuration.
If you want to build the configuration locally and deploy it to the remote server, just replace `--build-host aquamarinr` with `--build-host localhost`.
Instead of use ip address directly, we can also define some host aliases in `~/.ssh/config` or `/etc/ssh/ssh_config`, for example:
> ssh's config can be generated completely through Nix configuration, and this task is left to you.
```bash
cat ~/.ssh/config
# ......
Host ai
HostName 192.168.5.100
Port 22
Host aquamarine
HostName 192.168.5.101
Port 22
Host ruby
HostName 192.168.5.102
Port 22
Host kana
HostName 192.168.5.103
Port 22
```
Then we can use the host alias to deploy the configuration:
```bash
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo --verbose
```
## 4. Work with Makefile
> NOTE: Makefile's target name should not be the same as one of the file or directory in the current directory, otherwise the target will not be executed!
I use Makefile to manage the commands of my flake, which is very convenient.
For example, my Makefile looks like this:
```makefile
#
# NOTE: Makefile's target name should not be the same as one of the file or directory in the current directory,
# otherwise the target will not be executed!
#
############################################################################
#
# Nix commands related to the local machine
#
############################################################################
deploy:
nixos-rebuild switch --flake . --use-remote-sudo
debug:
nixos-rebuild switch --flake . --use-remote-sudo --show-trace --verbose
update:
nix flake update
history:
nix profile history --profile /nix/var/nix/profiles/system
gc:
# remove all generations older than 7 days
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d
# garbage collect all unused nix store entries
sudo nix store gc --debug
############################################################################
#
# Idols, Commands related to my remote distributed building cluster
#
############################################################################
add-idols-ssh-key:
ssh-add ~/.ssh/ai-idols
aqua: add-idols-ssh-key
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo
aqua-debug: add-idols-ssh-key
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo --show-trace --verbose
ruby: add-idols-ssh-key
nixos-rebuild --flake .#ruby --target-host ruby --build-host ruby switch --use-remote-sudo
ruby-debug: add-idols-ssh-key
nixos-rebuild --flake .#ruby --target-host ruby --build-host ruby switch --use-remote-sudo --show-trace --verbose
kana: add-idols-ssh-key
nixos-rebuild --flake .#kana --target-host kana --build-host kana switch --use-remote-sudo
kana-debug: add-idols-ssh-key
nixos-rebuild --flake .#kana --target-host kana --build-host kana switch --use-remote-sudo --show-trace --verbose
idols: aqua ruby kana
idols-debug: aqua-debug ruby-debug kana-debug
```
Save the above Makefile to the root directory of the flake, and then we can use `make deploy` to deploy the configuration to the local machine, and use `make idols` to deploy the configuration to all my remote servers.
## References
- [Tips&Tricks for NixOS Desktop - NixOS Discourse][Tips&Tricks for NixOS Desktop - NixOS Discourse]
[Tips&Tricks for NixOS Desktop - NixOS Discourse]: https://discourse.nixos.org/t/tips-tricks-for-nixos-desktop/28488

View file

@ -0,0 +1,30 @@
## NixOS 的优点 {#nix-advantages}
- 声明式配置OS as Code可以直接用 Git 管理配置
- NixOS 使用声明式配置来管理整个系统环境,只要你的配置文件不丢,系统就可以随时还原到任一历史状态(前面提过了,只有在 Nix 配置文件中声明了的状态才可被 NixOS 还原)。
- 为了提升系统的可复现能力Nix Flakes 还使用了 `flake.lock` 作为版本锁文件它记录了所有相关数据的数据源地址、hash 值。这个设计实际是从 cargo/npm 等一些编程语言的包管理设计中借鉴来的。
- 与 Docker 相比Dockerfile 实际是命令式的配置而且也不存在版本锁这样的东西Docker 容器环境的可复现实际依赖于直接通过 Docker Hub 分享构建结果Docker 镜像),这可比 Dockerfile 本身大多了。
- 高度便捷的系统自定义能力
- 通过改几行配置,就可以简单地更换系统的各种组件。这是因为 Nix 将底层的复杂操作全部封装在了 Nix 软件包中,只给用户提供了简洁且必要的声明式参数。
- 而且这种修改非常安全,你可以很方便地来回切换各种桌面环境(比如 gnome/kde/i3/sway几乎不会遇到坑。
- 可回滚
- 可以随时回滚到任一历史环境NixOS 甚至默认将所有旧版本都加入到了启动项,确保系统滚挂了也能随时回退。所以 Nix 也被认为是最稳定的包管理方式。
- 没有依赖冲突问题
- 因为 Nix 中每个软件包都拥有唯一的 hash其安装路径中也会包含这个 hash 值,因此可以多版本共存。
- 社区很活跃,第三方项目也挺丰富,官方包仓库 nixpkgs 贡献者众多,也有很多人分享自己的 Nix 配置,一遍浏览下来,整个生态给我一种发现新大陆的兴奋感。
## Nix 的缺点 {#nix-disadvantages}
- 学习成本高: 如果你希望系统完全可复现,并且避免各种不当使用导致的坑,那就需要学习了解 Nix 的整个设计,并以声明式的方式管理系统,不能无脑 `nix-env -i`(这类似 `apt-get install`)。
- 文档混乱: 首先 Nix Flakes 目前仍然是实验性特性,介绍它本身的文档目前比较匮乏, Nix 社区绝大多数文档都只介绍了旧的 `nix-env`/`nix-channel`,想直接从 Nix Flakes 开始学习的话,需要参考大量旧文档,从中提取出自己需要的内容。另外一些 Nix 当前的核心功能,官方文档都语焉不详(比如 `imports` 跟 Nixpkgs Module System想搞明白基本只能看源码了...
- 比较吃硬盘空间: 为了保证系统可以随时回退nix 默认总是保留所有历史环境,这非常吃硬盘空间。
- 这在桌面电脑上可能不是啥事,但是在资源受限的云服务器上可能会是个问题。
- 报错信息比较隐晦: 一般的报错提示还是比较清楚的,但是遇到好几次依赖版本有问题或者传参错误提示不出原因,`--show-trace` 直接输出一堆的内部堆栈,都花了很长时间才定位到,通过升级依赖版本或者修正参数后问题解决。
- 猜测导致这个问题的原因有两个,一是 Nix 是动态语言,各种参数都是运行时才确定类型。二是我用到的 flake 包的错误处理逻辑写得不太好,错误提示不清晰,一些隐晦的错误甚至通过错误堆栈也定位不到原因。
## 简单总结下 {#nix-simple-summary}
总的来说,我觉得 NixOS 适合那些有一定 Linux 使用经验与编程经验,并且希望对自己的系统拥有更强掌控力的开发者。
我不推荐没有任何 Linux 使用经验的新人直接上手 NixOS那可能会是一段糟糕的旅程。

View file

@ -0,0 +1,40 @@
---
footer: false
---
## Nix 与 NixOS 简介
Nix 是一个声明式的软件包管理器,用户需要通过某些配置声明好期望的环境状态,而 Nix 负责达成这个目标。
> 简单解释下什么是「声明式配置」,它是指用户只需要声明好自己想要的结果——比如说希望将 i3 桌面替换成 sway 桌面Nix 就会帮用户达成这个目标。
> 用户不需要关心底层细节(比如说 sway 需要安装哪些软件包,哪些 i3 相关的软件包需要卸载掉,哪些系统配置或环境变量需要针对 sway 做调整、如果使用了 Nvidia 显卡 Sway 参数要做什么调整才能正常运行等等Nix 会自动帮用户处理这些细节(当然这有个前提,就是 sway 跟 i3 相关的 nix 包设计良好)。
而基于 Nix 包管理器构建的 Linux 发行版 NixOS可以简单用 OS as Code 来形容,它通过声明式的 Nix 配置文件来描述整个操作系统的状态。
一个操作系统中有各种各样的软件包、配置文件、文本或二进制的数据,这些都是系统当前的状态,而声明式的配置能够管理到的,只是其中静态的那一部分。
而那些动态的数据(比如说 PostgreSQL/MySQL/MongoDB 的数据),显然是无法通过声明式配置管理的(总不能每次部署都直接删除掉所有未在配置中声明的新数据吧)。
因此 NixOS 实际也只支持通过声明式配置管理系统的部分状态,上面提到的各种动态数据,以及用户 Home 目录中的所有内容,都不受它管控(所以在你将 NixOS 切换到上一个版本时NixOS 不会对它们做任何操作)。
但是用户的 Home 目录中实际包含了许多重要的配置文件,用户当然会希望能使用 Nix 将它们给管理起来。
另一个重要的社区项目 [home-manager](https://github.com/nix-community/home-manager) 就填补了这块缺失,它被设计用于管理用户 Home 目录以及用户级别的软件包。
因为 Nix 声明式、可复现的特性Nix 不仅可用于管理桌面电脑的环境也有很多人用它管理开发编译环境、云上虚拟机、容器镜像构建等等Nix 官方的 [NixOps](https://github.com/NixOS/nixops) 与社区的 [deploy-rs](https://github.com/serokell/deploy-rs) 都是基于 Nix 实现的运维工具。
## 为什么选择 NixOS
好几年前就听说过 Nix 包管理器,它用 Nix 语言编写配置来管理系统依赖,此外基于 Nix 包管理器设计的 Linux 发行版 NixOS还能随时将系统回滚到任一历史状态额实际上这个回滚有些限制后面会介绍。 虽然听着很牛,但是不仅要多学一门语言,装个包还得写代码,当时觉得太麻烦就没研究。
但是我最近在使用 EndeavourOS 时遇到了一系列麻烦事儿,花了大量的精力去解决,搞得我精疲力尽。
我仔细一想,遇到的这些问题归根结底还是系统没有版本控制跟回滚机制,导致出了问题不能还原,就必须得各种查资料找 Bug手动修复系统状态。
所以我就决定干脆换成 NixOS.
切换到 NixOS 后,我对它那是相当的满意,腰也不疼了,背也不酸了...
最惊艳的是,现在我可以通过仅仅一行命令,就能在一台全新安装的 NixOS 主机上还原我的整个 i3 桌面环境以及所有我的常用软件!
NixOS 的回滚能力给了我非常大的底气,我现在再也不用怕把系统搞挂了(挂了直接回滚就恢复了),于是我又在 NixOS 尝试了许多新鲜玩意儿!
在以前 EndeavourOS 上我肯定是不太敢这么玩的,因为万一要是把系统玩出问题了,就必须手动修复系统状态,那可是相当麻烦。
所以这就是我选择 NixOS 的原因。

View file

@ -0,0 +1,14 @@
Nix 有多种安装方式:
1. 以包管理器的形式安装到 MacOS/Linux/WSL 三种系统上
2. 也可以直接安装 NixOS这是 Nix 官方推出的一个 Linux 发行版,使用 Nix 包管理器来管理整个系统环境。
我选择了直接使用 NixOS 的 ISO 镜像安装 NixOS 系统,从而最大程度上通过 Nix 管理整个系统环境。
安装步骤很简单,这里不多介绍,仅列一下我觉得比较有用的参考资料:
1. 国内镜像源:<https://mirrors.bfsu.edu.cn/help/nix/>
1. [Nix 的官方安装方式](https://nixos.org/download.html): 使用 bash 脚本编写, 目前2023-04-23为止 `nix-command` & `flakes` 仍然是实验性特性,需要手动开启。
1. 你需要参照 [Enable flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes) 的说明启用 `nix-command` & `flakes`
2. 官方不提供任何卸载手段,要在 Linux/MacOS 上卸载 Nix你需要手动删除所有相关的文件、用户以及用户组
2. [The Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer): 第三方使用 Rust 编写的 installer, 默认启用 `nix-command` & `flakes`,并且提供了卸载命令。

View file

@ -0,0 +1,28 @@
### Advantages of Nix Flakes
- **Declarative configuration, Environment as Code, can be managed with Git**
- As long as the configuration files are not lost, the system can be restored to any historical state at any time(except the state that are NOT managed by NixOS, such as docker containers, postgresql data, etc...)
- Nix Flakes lock dependences's version through a lock file named `flake.lock`, to ensure that the system is reproducible, this idea actually borrows from some package managers such as npm, cargo, etc.
- Compared with Docker, Nix Flakes provides a much stronger guarantee for the reproducibility of build results, because Dockerfile is actually an imperative configuration and there is no such thing as `flake.lock` in Docker, Docker's reproducibility relies on sharing the build result(which is MUCH MORE LARGER than Dockerfile itself) through image registry(e.g. DockerHub).
- **Highly convenient system customization capability**
- By changing a few lines of configuration, various components of NixOS can be easily customized. This is because Nix encapsulates all the underlying complex operations in nix packages and only exports concise and necessary declarative parameters.
- Moreover, this modification is very safe. An example is that one NixOS user on the V2EX forum stated that "[**on NixOS, switching between different desktop environments is very simple and clean, and it is very safe. I often switch between gnome/kde/sway.**](https://www.v2ex.com/t/938569#r_13053251)"
- **Rollback**: The system can be rolled back to any historical environment at any time, and NixOS even adds all old versions to the boot options by default to ensure that the system can be rolled back at any time even though it crashes. Therefore, NixOS is also considered one of the most stable Linux Systems.
- **No dependency conflicts**: Because each software package in Nix has a unique hash, its installation path also includes this hash value, so multiple versions can coexist.
- **The community is very active, and there are quite a few third-party projects**. The official package repository, nixpkgs, has many contributors, and many people share their Nix configuration on Github/Gitlab. After browsing through it, the entire ecosystem gives me a sense of excitement in discovering a new continent.
{{< figure src="nixos-bootloader.avif" caption="All historical versions are listed in the boot options of NixOS. Image from [NixOS Discourse - 10074](https://discourse.nixos.org/t/how-to-make-uefis-grub2-menu-the-same-as-bioss-one/10074)" >}}
### Disadvantages of Nix Flakes
- **Relatively high learning curve:**: If you want the system to be completely reproducible and avoid pitfalls caused by improper use, you need to learn about the entire design of Nix and manage the system in a declarative manner. You cannot blindly use `nix-env -i` (which is similar to `apt-get install`).
- **Chaotic documentation**: Flakes is still an experimental feature, and there are currently few documents introducing it, Most of the Nix community's documentation only introduces the old cli such as `nix-env`/`nix-channel`. If you want to start learning Nix directly from Flakes, you need to refer to a large number of old documents and extract what you need from them. In addition, some of Nix's current core functions are not well-documented (such as `imports` and Nix Module System), to figure out what it does, it is best to look at the source code...
- ~~Relatively few packages~~: Retract this one. The official claim is that nixpkgs has [80000+](https://search.nixos.org/packages) packages, and indeed, most packages can be found in nixpkgs.
- **Relatively high disk space usage**: To ensure that the system can be rolled back at any time, Nix preserves all historical environments by default, which can take up a lot of disk space. Although you can manually clean up old historical environments periodically with `nix-collect-garbage`, it is still recommended to buy a larger hard drive.
### Summary
Generally speaking, I think NixOS is suitable for developers who have some experience in using Linux and programming and want to have more control over their systems.
I don't recommend you getting started with NixOS if you are new to Linux, it can be a very painful journey.

View file

@ -0,0 +1,70 @@
## Warning about Flakes
The flakes experimental feature is a major development for Nix, it introduces a policy for managing dependencies between Nix expressions, it improves reproducibility, composability and usability in the Nix ecosystem. Although it's still an experimental feature, flakes have been widely used by the Nix community.[^1]
It's Flakes is one of the most significant changes the nix project has ever seen.[^2]
The benefits of Flakes are obvious, and the entire NixOS community likes it very much. Currently, more than half of the users are using Flakes[^3], so we're pretty sure that Flakes will never be deprecated.
:warning: But **Flakes is still an experimental feature**, there are still many problems with it, and it is likely to introduce some breaking changes in the process of stablizing it, and its uncertain how greatly the breaking changes will be.
So overall, I still recommend everyone to use Flakes, but be prepared for the problems that may be caused by the upcomming breaking changes.
## III. Nix Flakes and the classic Nix
As `nix-command` & `flakes` are still experimental features, the official documentation does not cover them in detail, and the community's documentation about them is also very scattered.
However, from the perspective of reproducibility and ease of management and maintenance, the classic Nix package structure and cli are no longer recommended for use.
So I will not introduce the usage of the classic Nix. It's recommended that beginners just start with `nix-command` & `flakes` and ignore all the contents about the classic Nix.
Here are the classic Nix commands and related concepts that are no longer needed after you enabling `nix-command` and `flakes`, when searching for information, you can safely ignore them:
1. `nix-channel`: `nix-channel` is similar to other package management tools such as apt/yum/pacman, managing software package versions through stable/unstable/test channels.
1. In Flakes, the functionality of `nix-channel` is completely replaced by `inputs` in `flake.nix`.
2. `nix-env`: `nix-env` is a core command-line tool for classic Nix used to manage software packages in the user environment. It installs packages from the data sources added by `nix-channel`, so the installed package's version are influenced by the channel. Packages installed with `nix-env` are not automatically recorded in Nix's declarative configuration and are entirely outside of its control, making them difficult to reproduce on other machines. Therefore, it is not recommended to use this tool.
1. The corresponding command in Flakes is `nix profile`, it's not recommended to use it either.
3. `nix-shell`: `nix-shell` is used to create a temporary shell environment, which is useful for development and testing.
1. In Flakes, it is replaced by `nix develop` and `nix shell`.
4. `nix-build`: `nix-build` is used to build Nix packages, and it places the build results in `/nix/store`, but it does not record them in Nix's declarative configuration.
1. In Flakes, `nix-build` is replaced by `nix build`.
5. ...
> maybe `nix-env -qa` is still useful some times, which returns all packages installed in the System.
## IX. When will flakes stablized {#when-will-flakes-stablized}
I dived into some details about flakes:
- https://github.com/NixOS/rfcs/pull/136: A plan to stabilize Flakes and the new CLI incrementally, still WIP.
- https://discourse.nixos.org/t/why-are-flakes-still-experimental/29317: A post, Why are flakes still experimental?
- https://grahamc.com/blog/flakes-are-an-obviously-good-thing/: Flakes are such an obviously good thing... but the design and development process should be better.
- [Draft: 1 year roadmap - NixOS Foundation](https://nixos-foundation.notion.site/1-year-roadmap-0dc5c2ec265a477ea65c549cd5e568a9) A roadmap of nixos fundation, which includes plan about the stabilization of flakes.
After reading all of these, I feel like that flakes will eventually be stabilized in one or two years, maybe with some breaking changes.
[^1]: [Flakes - NixOS Wiki](https://nixos.wiki/index.php?title=Flakes)
[^2]: [Flakes are such an obviously good thing](https://grahamc.com/blog/flakes-are-an-obviously-good-thing/)
[^3]: [Draft: 1 year roadmap - NixOS Foundation](https://nixos-foundation.notion.site/1-year-roadmap-0dc5c2ec265a477ea65c549cd5e568a9)
[^1]: [Flakes - NixOS Wiki](https://nixos.wiki/index.php?title=Flakes)
[^2]: [Flakes are such an obviously good thing](https://grahamc.com/blog/flakes-are-an-obviously-good-thing/)

View file

@ -0,0 +1,148 @@
## VII. Usage of Nix Flakes
Up to now, we have written a lot of configuration with Flakes to manage NixOS. Here is a brief introduction to the more detailed content of the Flakes, as well as the new command lines commonly used with flakes.
### 1. Flake inputs {#flake-inputs}
The `inputs` in `flake.nix` is a attribute set, used to specify the dependencies of the current flake, there are many types of `inputs`, for example:
```nix
{
inputs = {
# use master branch of the GitHub repository as input, this is the most common input format
nixpkgs.url = "github:Mic92/nixpkgs/master";
# Git URL, can be used for any Git repository based on https/ssh protocol
git-example.url = "git+https://git.somehost.tld/user/path?ref=branch&rev=fdc8ef970de2b4634e1b3dca296e1ed918459a9e";
# The above example will also copy .git, use this for (shallow) local Git repos
git-directory-example.url = "git+file:/path/to/repo?shallow=1";
# Local directories (for absolute paths you can omit 'path:')
directory-example.url = "path:/path/to/repo";
bar = {
url = "github:foo/bar/branch";
# if the input is not a flake, you need to set flake=false
flake = false;
};
sops-nix = {
url = "github:Mic92/sops-nix";
# The `follows` keyword in inputs is used for inheritance.
# Here, `inputs.nixpkgs` of sops-nix is kept consistent with the `inputs.nixpkgs` in
# the current flake, to avoid problems caused by different versions of nixpkgs.
inputs.nixpkgs.follows = "nixpkgs";
};
# Pin flakes to a specific revision
nix-doom-emacs = {
url = "github:vlaci/nix-doom-emacs?rev=238b18d7b2c8239f676358634bfb32693d3706f3";
flake = false;
};
# To use a subdirectory of a repo, pass `dir=xxx`
nixpkgs.url = "github:foo/bar?dir=shu";
}
}
```
### 2. Flake outputs
the `outputs` in `flake.nix` are what a flake produces as part of its build. Each flake can have many different outputs simultaneously, including but not limited to:
- Nix packages: named `apps.<system>.<name>`, `packages.<system>.<name>`, or `legacyPackages.<system>.<name>`
- we can build a package by command `nix build .#<name>`
- Nix Helper Functions: named `lib`., which means a library for other flakes.
- Nix development environments: named `devShells`
- `devShells` can be used by command `nix develop`, will be introduced later.
- NixOS configuration: named `nixosConfiguration`
- `nixosConfiguration` will be used by command `nixos-rebuild switch --flake .#<name>`
- Nix templates: named `templates`
- templates can be used by command `nix flake init --template <reference>`
- Other user defined outputs, may be parsed by other nix-related tools.
An example copy from NixOS Wiki:
```nix
{ self, ... }@inputs:
{
# Executed by `nix flake check`
checks."<system>"."<name>" = derivation;
# Executed by `nix build .#<name>`
packages."<system>"."<name>" = derivation;
# Executed by `nix build .`
packages."<system>".default = derivation;
# Executed by `nix run .#<name>`
apps."<system>"."<name>" = {
type = "app";
program = "<store-path>";
};
# Executed by `nix run . -- <args?>`
apps."<system>".default = { type = "app"; program = "..."; };
# Formatter (alejandra, nixfmt or nixpkgs-fmt)
formatter."<system>" = derivation;
# Used for nixpkgs packages, also accessible via `nix build .#<name>`
legacyPackages."<system>"."<name>" = derivation;
# Overlay, consumed by other flakes
overlays."<name>" = final: prev: { };
# Default overlay
overlays.default = {};
# Nixos module, consumed by other flakes
nixosModules."<name>" = { config }: { options = {}; config = {}; };
# Default module
nixosModules.default = {};
# Used with `nixos-rebuild --flake .#<hostname>`
# nixosConfigurations."<hostname>".config.system.build.toplevel must be a derivation
nixosConfigurations."<hostname>" = {};
# Used by `nix develop .#<name>`
devShells."<system>"."<name>" = derivation;
# Used by `nix develop`
devShells."<system>".default = derivation;
# Hydra build jobs
hydraJobs."<attr>"."<system>" = derivation;
# Used by `nix flake init -t <flake>#<name>`
templates."<name>" = {
path = "<store-path>";
description = "template description goes here?";
};
# Used by `nix flake init -t <flake>`
templates.default = { path = "<store-path>"; description = ""; };
}
```
### 3. Flake Command Line Usage
after enabled `nix-command` & `flake`, you can use `nix help` to get all the info of [New Nix Commands][New Nix Commands], some useful examples are listed below:
```bash
# `nixpkgs#ponysay` means `ponysay` from `nixpkgs` flake.
# [nixpkgs](https://github.com/NixOS/nixpkgs) contains `flake.nix` file, so it's a flake.
# `nixpkgs` is a falkeregistry id for `github:NixOS/nixpkgs/nixos-unstable`.
# you can find all the falkeregistry ids at <https://github.com/NixOS/flake-registry/blob/master/flake-registry.json>
# so this command means install and run package `ponysay` in `nixpkgs` flake.
echo "Hello Nix" | nix run "nixpkgs#ponysay"
# this command is the same as above, but use a full flake URI instead of falkeregistry id.
echo "Hello Nix" | nix run "github:NixOS/nixpkgs/nixos-unstable#ponysay"
# instead of treat flake package as an application,
# this command use `devShells.example` in flake `zero-to-nix`'s outputs, to setup the development environment,
# and then open a bash shell in that environment.
nix develop "github:DeterminateSystems/zero-to-nix#example"
# instead of using a remote flake, you can open a bash shell using the flake located in the current directory.
mkdir my-flake && cd my-flake
## init a flake with template
nix flake init --template "github:DeterminateSystems/zero-to-nix#javascript-dev"
# open a bash shell using the flake in current directory
nix develop
# or if your flake has multiple devShell outputs, you can specify which one to use.
nix develop .#example
# build package `bat` from flake `nixpkgs`, and put a symlink `result` in the current directory.
mkdir build-nix-package && cd build-nix-package
nix build "nixpkgs#bat"
# build a local flake is the same as nix develop, skip it
```
[Zero to Nix - Determinate Systems][Zero to Nix - Determinate Systems] is a brand new guide to get started with Nix & Flake, recommended to read for beginners.

956
docs/zh/nixos/index.md Normal file
View file

@ -0,0 +1,956 @@
## VI. Managing the system declaratively
> https://nixos.wiki/wiki/Overview_of_the_NixOS_Linux_distribution
After learning the basics of the Nix language, we can start using it to configure our NixOS. The default configuration for NixOS is located at `/etc/nixos/configuration.nix`, which contains all the declarative configuration for the system, such as time zone, language, keyboard layout, network, users, file system, boot options, etc.
If we want to modify the system state in a reproducible way (**which is also the most recommended way**), we need to manually edit `/etc/nixos/configuration.nix`, and then execute `sudo nixos-rebuild switch` to apply the modified configuration, it will generate a new system environment based on the configuration file we modified, sets the new environment as the default one, and also preserves & added the previous environment into the boot options of grub/sytemd-boot. This ensures we can always roll back to the old environment(even if the new environment fails to start).
`/etc/nixos/configuration.nix` is the classic method to configure NixOS, which relies on data sources configured by `nix-channel` and has no version-locking mechanism, making it difficult to ensure the reproducibility of the system. **A better approach is to use Flakes**, which can ensure the reproducibility of the system and make it easy to manage the configuration.
Now first, let's learn how to manage NixOS through the classic method, `/etc/nixos/configuration.nix`, and then migrate to the more advanced Flakes.
### 1. Configuring the system using `/etc/nixos/configuration.nix`
As I mentioned earlier, this is the classic method to configured NixOS, and also the default method currently used by NixOS. It relies on data sources configured by `nix-channel` and has no version-locking mechanism, making it difficult to ensure the reproducibility of the system.
For example, to enable ssh and add a user "ryan", simply add the following content into `/etc/nixos/configuration.nix`:
```nix
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Omit the previous configuration.......
# add user ryan
users.users.ryan = {
isNormalUser = true;
description = "ryan";
extraGroups = [ "networkmanager" "wheel" ];
openssh.authorizedKeys.keys = [
# replace with your own public key
"ssh-ed25519 <some-public-key> ryan@ryan-pc"
];
packages = with pkgs; [
firefox
# thunderbird
];
};
# enable openssh-server
services.openssh = {
enable = true;
permitRootLogin = "no"; # disable root login
passwordAuthentication = false; # disable password login
openFirewall = true;
forwardX11 = true; # enable X11 forwarding
};
# omit the rest of the configuration.......
}
```
In this configuration, we declared that we want to enable the openssh service, add an ssh public key for the user ryan, and disable password login.
Now, let's run `sudo nixos-rebuild switch` to deploy the modified configuration, and then we can login to the system using ssh with the ssh keys we configured.
Any reproducible changes to the system can be made by modifying `/etc/nixos/configuration.nix` and deploying the changes by running `sudo nixos-rebuild switch`.
All configuration options in `/etc/nixos/configuration.nix` can be found in the following places:
- By searching on Google, such as `Chrome NixOS`, which will provide NixOS informations related to Chrome. Generally, the NixOS Wiki and the source code of Nixpkgs will be among the top results.
- By searching for keywords in [NixOS Options Search](https://search.nixos.org/options).
- For system-level configuration, relevant documentation can be found in [Configuration - NixOS Manual](https://nixos.org/manual/nixos/unstable/index.html#ch-configuration).
- By searching for keywords directly in the source code of [nixpkgs](https://github.com/NixOS/nixpkgs) on GitHub.
### 2. Enabling NixOS Flakes Support
Compared to the default configuration approach of NixOS, Flakes provide better reproducibility and a clearer package structure that is easier to maintain. Therefore, it is recommended to manage NixOS with Flakes.
However, as Flakes is still an experimental feature currently, it's not enabled by default yet, we need to enable it manually by modifying `/etc/nixos/configuration.nix`, example as follows:
```nix
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# omit the previous configuration.......
# enable Flakes and the new command line tool
nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [
# Flakes uses git to pull dependencies from data sources, so git must be installed first
git
vim
wget
curl
];
# omit the rest of the configuration.......
}
```
Now run `sudo nixos-rebuild switch` to apply the changes, and then you can write the configuration for NixOS with Flakes.
### 3. Switching System Configuration to `flake.nix`
After enabling `flakes`, `sudo nixos-rebuild switch` will try to read`/etc/nixos/flake.nix` first every time you run it, if not found, it will fallback to `/etc/nixos/configuration.nix`.
Now to learn how to write a flake, let's take a look at the official flake templates provided by Nix. First, check which templates are available:
```bash
nix flake show templates
```
The templates `templates#full` contains all possible usecases, let's take a look at it:
```bash
nix flake init -t templates#full
cat flake.nix
```
After reading this example, let's create a file `/etc/nixos/flake.nix` and copy the content of the example into it.
With `/etc/nixos/flake.nix`, all system modifications will be taken over by Flakes from now on.
The template we copied CAN NOT be used directly, we need to modify it to make it work, an example of `/etc/nixos/flake.nix` is as follows:
```nix
{
description = "Ryan's NixOS Flake";
# This is the standard format for flake.nix. `inputs` are the dependencies of the flake,
# and `outputs` function will return all the build results of the flake.
# Each item in `inputs` will be passed as a parameter to the `outputs` function after being pulled and built.
inputs = {
# There are many ways to reference flake inputs. The most widely used is github:owner/name/reference,
# which represents the GitHub repository URL + branch/commit-id/tag.
# Official NixOS package source, using nixos-unstable branch here
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# home-manager, used for managing user configuration
home-manager = {
url = "github:nix-community/home-manager/release-22.11";
# The `follows` keyword in inputs is used for inheritance.
# Here, `inputs.nixpkgs` of home-manager is kept consistent with the `inputs.nixpkgs` of the current flake,
# to avoid problems caused by different versions of nixpkgs.
inputs.nixpkgs.follows = "nixpkgs";
};
};
# `outputs` are all the build result of the flake.
# A flake can have many use cases and different types of outputs.
# parameters in `outputs` are defined in `inputs` and can be referenced by their names.
# However, `self` is an exception, This special parameter points to the `outputs` itself (self-reference)
# The `@` syntax here is used to alias the attribute set of the inputs's parameter, making it convenient to use inside the function.
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
# By default, NixOS will try to refer the nixosConfiguration with its hostname.
# so the system named `nixos-test` will use this configuration.
# However, the configuration name can also be specified using `sudo nixos-rebuild switch --flake /path/to/flakes/directory#<name>`.
# The `nixpkgs.lib.nixosSystem` function is used to build this configuration, the following attribute set is its parameter.
# Run `sudo nixos-rebuild switch --flake .#nixos-test` in the flake's directory to deploy this configuration on any NixOS system
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# The Nix module system can modularize configuration, improving the maintainability of configuration.
#
# Each parameter in the `modules` is a Nix Module, and there is a partial introduction to it in the nixpkgs manual:
# <https://nixos.org/manual/nixpkgs/unstable/#module-system-introduction>
# 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.
# If a Module is a function, this function can only have the following parameters:
#
# lib: the nixpkgs function library, which provides many useful functions for operating Nix expressions
# https://nixos.org/manual/nixpkgs/stable/#id-1.4
# config: all config options of the current flake
# options: all options defined in all NixOS Modules in the current flake
# pkgs: a collection of all packages defined in nixpkgs.
# you can assume its default value is `nixpkgs.legacyPackages."${system}"` for now.
# can be customed by `nixpkgs.pkgs` option
# modulesPath: the default path of nixpkgs's builtin modules folder,
# used to import some extra modules from nixpkgs.
# this parameter is rarely used, you can ignore it for now.
#
# Only these parameters can be passed by default.
# If you need to pass other parameters, you must use `specialArgs` by uncomment the following line
# specialArgs = {...} # pass custom arguments into sub module.
modules = [
# Import the configuration.nix we used before, so that the old configuration file can still take effect.
# Note: /etc/nixos/configuration.nix itself is also a Nix Module, so you can import it directly here
./configuration.nix
];
};
};
};
}
```
Here we defined a NixOS system called `nixos-test`, whose configuration file is `./configuration.nix`, which is the classic configuration we modified before, so we can still make use of it.
Now run `sudo nixos-rebuild switch` to apply the configuration, and no changes will be made to the system, because we imported the old configuration file in `/etc/nixos/flake.nix`, so the actual state we declared remains unchanged.
### 4. Manage system software through Flakes
After the switch, we can now manage the system through Flakes. The most common requirement for managing a system is to install softwares. We have seen how to install packages through `environment.systemPackages` before, and these packages are all from the official nixpkgs repository.
Now let's learn how to install packages from other sources through Flakes. This is much more flexible than installing from nixpkgs directly. The most obvious benefit is that you can easily set the version of the software.
Use [helix](https://github.com/helix-editor/helix) editor as an example, first we need to add the helix as an input into `flake.nix`:
```nix
{
description = "NixOS configuration of Ryan Yin";
# ......
inputs = {
# ......
# helix editor, use the tag 23.05
helix.url = "github:helix-editor/helix/23.05";
};
outputs = inputs@{ self, nixpkgs, ... }: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# set all inputs parameters as specialArgs of all sub module
# so that we can use `helix` input in sub modules
specialArgs = inputs;
modules = [
./configuration.nix
];
};
};
};
}
```
Then udpate `configuration.nix` to install `helix` from the input `helix`:
```nix
# Nix will automatically inject `helix` from specialArgs
# into the third parameter of this function through name matching
{ config, pkgs, helix, ... }:
{
# omit other configuration......
environment.systemPackages = with pkgs; [
git
vim
wget
curl
# install helix from the input `helix`
helix.packages."${pkgs.system}".helix
];
# omit other configuration......
}
```
Now deploy the changes by `sudo nixos-rebuild switch`, and then we can start the helix editor by `helix` command.
### 5. Add Custom Cache Mirror
> You can skip this section if you don't need to customize the cache mirror.
To speed up package building, Nix provides <https://cache.nixos.org> to cache build results to avoid build every packages locally.
With the NixOS's classic configuration method, other cache sources can be added by using `nix-channel`, but Flakes avoids using any system-level configuration and environment variables to ensure that its build results are not affected by the environment(so the build results are reproducible).
Therefore, to customize the cache source, we must add the related configuration in `flake.nix` by using the parameter `nixConfig`. An example is as follows:
```nix
{
description = "NixOS configuration of Ryan Yin";
# 1. To ensure purity, Flakes does not rely on the system's `/etc/nix/nix.conf`, so we have to set related configuration here.
# 2. To ensure security, flake allows only a few nixConfig parameters to be set directly by default.
# you need to add `--accept-flake-config` when executing the nix command,
# otherwise all other parameters will be ignored, and an warning will printed by nix.
nixConfig = {
experimental-features = [ "nix-command" "flakes" ];
substituters = [
# replace official cache with a mirror located in China
"https://mirrors.bfsu.edu.cn/nix-channels/store"
"https://cache.nixos.org/"
];
extra-substituters = [
# nix community's cache server
"https://nix-community.cachix.org"
];
extra-trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
inputs = {
# omit some configuration...
};
outputs = {
# omit some configuration...
};
}
```
After the modification, run `sudo nixos-rebuild switch` to apply the updates.
### 6. Install home-manager
As I mentioned earlier, NixOS can only manage system-level configuration, to manage the Home directory(user-level configuration), we need to install home-manager.
According to the official document [Home Manager Manual](https://nix-community.github.io/home-manager/index.htm), in order to install home-manager as a module of NixOS, we need to create `/etc/nixos/home.nix` first, an example content shown below:
```nix
{ config, pkgs, ... }:
{
# TODO please change the username & home direcotry to your own
home.username = "ryan";
home.homeDirectory = "/home/ryan";
# link the configuration file in current directory to the specified location in home directory
# home.file.".config/i3/wallpaper.jpg".source = ./wallpaper.jpg;
# link all files in `./scripts` to `~/.config/i3/scripts`
# home.file.".config/i3/scripts" = {
# source = ./scripts;
# recursive = true; # link recursively
# executable = true; # make all files executable
# };
# encode the file content in nix configuration file directly
# home.file.".xxx".text = ''
# xxx
# '';
# set cursor size and dpi for 4k monitor
xresources.properties = {
"Xcursor.size" = 16;
"Xft.dpi" = 172;
};
# basic configuration of git, please change to your own
programs.git = {
enable = true;
userName = "Ryan Yin";
userEmail = "xiaoyin_c@qq.com";
};
# Packages that should be installed to the user profile.
home.packages = [
# here is some command line tools I use frequently
# feel free to add your own or remove some of them
neofetch
nnn # terminal file manager
# archives
zip
xz
unzip
p7zip
# utils
ripgrep # recursively searches directories for a regex pattern
jq # A lightweight and flexible command-line JSON processor
yq-go # yaml processer https://github.com/mikefarah/yq
exa # A modern replacement for ls
fzf # A command-line fuzzy finder
# networking tools
mtr # A network diagnostic tool
iperf3
dnsutils # `dig` + `nslookup`
ldns # replacement of `dig`, it provide the command `drill`
aria2 # A lightweight multi-protocol & multi-source command-line download utility
socat # replacement of openbsd-netcat
nmap # A utility for network discovery and security auditing
ipcalc # it is a calculator for the IPv4/v6 addresses
# misc
cowsay
file
which
tree
gnused
gnutar
gawk
zstd
gnupg
# nix related
#
# it provides the command `nom` works just like `nix
# with more details log output
nix-output-monitor
# productivity
hugo # static site generator
glow # markdown previewer in terminal
btop # replacement of htop/nmon
iotop # io monitoring
iftop # network monitoring
# system call monitoring
strace # system call monitoring
ltrace # library call monitoring
lsof # list open files
# system tools
sysstat
lm_sensors # for `sensors` command
ethtool
pciutils # lspci
usbutils # lsusb
];
# starship - an customizable prompt for any shell
programs.starship = {
enable = true;
settings = {
add_newline = false;
aws.disabled = true;
gcloud.disabled = true;
line_break.disabled = true;
};
};
# alacritty - a cross-platform, GPU-accelerated terminal emulator
programs.alacritty = {
enable = true;
env.TERM = "xterm-256color";
font = {
size = 12;
draw_bold_text_with_bright_colors = true;
};
scrolling.multiplier = 5;
selection.save_to_clipboard = true;
};
programs.bash = {
enable = true;
enableCompletion = true;
bashrcExtra = ''
export PATH="$PATH:$HOME/bin:$HOME/.local/bin:$HOME/go/bin"
'';
# set some aliases, feel free to add more or remove some
shellAliases = {
urldecode = "python3 -c 'import sys, urllib.parse as ul; print(ul.unquote_plus(sys.stdin.read()))'";
urlencode = "python3 -c 'import sys, urllib.parse as ul; print(ul.quote_plus(sys.stdin.read()))'";
httpproxy = "export https_proxy=http://127.0.0.1:7890; export http_proxy=http://127.0.0.1:7890;";
};
};
# This value determines the home Manager release that your
# configuration is compatible with. This helps avoid breakage
# when a new home Manager release introduces backwards
# incompatible changes.
#
# You can update home Manager without changing this value. See
# the home Manager release notes for a list of state version
# changes in each release.
home.stateVersion = "22.11";
# Let home Manager install and manage itself.
programs.home-manager.enable = true;
}
```
After adding `/etc/nixos/home.nix`, you need to import this new configuration file in `/etc/nixos/flake.nix` to make use of it, use the following command to generate an example in the current folder for reference:
```shell
nix flake new example -t github:nix-community/home-manager#nixos
```
After adjusting the parameters, the content of `/etc/nixos/flake.nix` is as follows:
```nix
{
description = "NixOS configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ nixpkgs, home-manager, ... }: {
nixosConfigurations = {
# TODO please change the hostname to your own
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
# make home-manager as a module of nixos
# so that home-manager configuration will be deployed automatically when executing `nixos-rebuild switch`
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# TODO replace ryan with your own username
home-manager.users.ryan = import ./home.nix;
# Optionally, use home-manager.extraSpecialArgs to pass arguments to home.nix
}
];
};
};
};
}
```
Then run `sudo nixos-rebuild switch` to apply the configuration, and home-manager will be installed automatically.
After the installation, all user-level packages and configuration can be managed through `/etc/nixos/home.nix`. When running `sudo nixos-rebuild switch`, the configuration of home-manager will be applied automatically. (**It's not necessary to run `home-manager switch` manually**!)
To find the options we can use in `home.nix`, referring to the following documents:
- [Home Manager - Appendix A. Configuration Options](https://nix-community.github.io/home-manager/options.html): A list of all options, it is recommended to search for keywords in it.
- [home-manager](https://github.com/nix-community/home-manager): Some options are not listed in the official documentation, or the documentation is not clear enough, you can directly search and read the corresponding source code in this home-manager repo.
### 7. Modular NixOS configuration
At this point, the skeleton of the entire system is basically configured. The current configuration structure in `/etc/nixos` should be as follows:
```
$ tree
.
├── flake.lock
├── flake.nix
├── home.nix
└── configuration.nix
```
The functions of these four files are explained below:
- `flake.lock`: An automatically generated version-lock file, which records all input sources, hash values, and version numbers of the entire flake to ensure that the system is reproducible.
- `flake.nix`: The entry file, which will be recognized and deployed when executing `sudo nixos-rebuild switch`.
- See [Flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes) for all options of flake.nix.
- `configuration.nix`: Imported as a nix module in flake.nix, all system-level configuration are currently written here.
- See [Configuration - NixOS Manual](https://nixos.org/manual/nixos/unstable/index.html#ch-configuration) for all options of configuration.nix.
- `home.nix`: Imported by home-manager as the configuration of the user `ryan` in flake.nix, that is, it contains all the configuration of `ryan`, and is responsible for managing `ryan`'s home folder.
- See [Appendix A. Configuration Options - home Manager](https://nix-community.github.io/home-manager/options.html) for all options of home.nix.
By modifying these files, you can change the status of the system and the home directory declaratively.
As the configuration increases, it will be difficult to maintain the configuration by relying solely on `configuration.nix` and `home.nix`. Therefore, a better solution is to use the nix module system to split the configuration into multiple modules and write them in a classified manner.
nix module system provide a paramter, `imports`, which accept a list of `.nix` files, and merge all the configuration defined in these files into the current nix module. Note that the word used here is "**merge**", which means that `imports` will **NOT** simply overwrite the duplicate configuration, but handle them more reasonably. For example, if I define `program.packages = [...]` in multiple modules, then `imports` will merge all `program.packages` defined in all nix modules into one list. Not only lists can be merged correctly, but attribute sets can also be merged correctly. The specific behavior can be explored by yourself.
> I only found a description of `imports` in [nixpkgs-unstable official manual - evalModules parameters](https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules-parameters): `A list of modules. These are merged together to form the final configuration.`, it's a bit ambiguous...
With the help of `imports`, we can split `home.nix` and `configuration.nix` into multiple nix modules defined in diffrent `.nix` files.
Use [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2) as an example, which is the configuration of my previous NixOS system with i3 window manager. The structure of it is as follows:
```shell
├── flake.lock
├── flake.nix
├── home
│ ├── default.nix # here we import all submodules by imports = [...]
│ ├── fcitx5 # fcitx5 input method's configuration
│ │ ├── default.nix
│ │ └── rime-data-flypy
│ ├── i3 # i3 window manager's configuration
│ │ ├── config
│ │ ├── default.nix
│ │ ├── i3blocks.conf
│ │ ├── keybindings
│ │ └── scripts
│ ├── programs
│ │ ├── browsers.nix
│ │ ├── common.nix
│ │ ├── default.nix # here we import all modules in programs folder by imports = [...]
│ │ ├── git.nix
│ │ ├── media.nix
│ │ ├── vscode.nix
│ │ └── xdg.nix
│ ├── rofi # rofi launcher's configuration
│ │ ├── configs
│ │ │ ├── arc_dark_colors.rasi
│ │ │ ├── arc_dark_transparent_colors.rasi
│ │ │ ├── power-profiles.rasi
│ │ │ ├── powermenu.rasi
│ │ │ ├── rofidmenu.rasi
│ │ │ └── rofikeyhint.rasi
│ │ └── default.nix
│ └── shell # shell/terminal related configuration
│ ├── common.nix
│ ├── default.nix
│ ├── nushell
│ │ ├── config.nu
│ │ ├── default.nix
│ │ └── env.nu
│ ├── starship.nix
│ └── terminals.nix
├── hosts
│ ├── msi-rtx4090 # My main machine's configuration
│ │ ├── default.nix # This is the old configuration.nix, but most of the content has been split out to modules.
│ │ └── hardware-configuration.nix # hardware & disk related configuration, autogenerated by nixos
│ └── nixos-test # my test machine's configuration
│ ├── default.nix
│ └── hardware-configuration.nix
├── modules # some common NixOS modules that can be reused
│ ├── i3.nix
│ └── system.nix
└── wallpaper.jpg # wallpaper
```
For more details, see [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2).
#### 7.1. `mkOverride`, `lib.mkDefault` and `lib.mkForce`
You may found some people use `lib.mkDefault` `lib.mkForce` to define values in Nix files, as their names suggest, `lib.mkDefault` and `lib.mkForce` are used to set default values or force values of options.
You can read the source code of `lib.mkDefault` and `lib.mkForce` to understand them by running `nix repl -f '<nixpkgs>'` and then enter `:e lib.mkDefault`(To learn the basic usage of `nix repl`, just type `:?` to see the help information).
its source code is as follows:
```nix
# ......
mkOverride = priority: content:
{ _type = "override";
inherit priority content;
};
mkOptionDefault = mkOverride 1500; # priority of option defaults
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
mkForce = mkOverride 50;
mkVMOverride = mkOverride 10; # used by nixos-rebuild build-vm
# ......
```
So `lib.mkDefault` is used to set default values of options, it has a priority of 1000 internally,
and `lib.mkForce` is used to force values of options, it has a priority of 50 internally.
If you just set a value of an option directly, it will be set with a default priority of 1000(the same as `lib.mkDefault`).
the lower the `priority`'s value is, the higher the actual priority is, so `lib.mkForce` has a higher priority than `lib.mkDefault`.
If you defined multiple values with the same priority, Nix will throw an error.
They are useful to modularize the configuration, as you can set default values in a low-level module(base module), and force values in a high-level module.
For example, I defined some default values in <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-server.nix#L30>:
```nix
{ lib, pkgs, ... }:
{
# ......
nixpkgs.config.allowUnfree = lib.mkDefault false;
# ......
}
```
And for my dekstop machine, I force the values to another value in <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-desktop.nix#L15>:
```nix
{ lib, pkgs, ... }:
{
# import the base module
imports = [
./core-server.nix
];
# override the default value defined in the base module
nixpkgs.config.allowUnfree = lib.mkForce true;
# ......
}
```
#### 7.2 `lib.mkOrder`, `lib.mkBefore` and `lib.mkAfter`
`lib.mkBefore` and `lib.mkAfter` are used to set the merge order of **list-type options**, just like `lib.mkDefault` and `lib.mkForce`, they're also useful to modularize the configuration.
I said before that if you defined multiple values with the same **override priority**, Nix will throw an error.
But with `lib.mkOrder`, `lib.mkBefore` or `lib.mkAfter`, you can define multiple values with the same override priority, they will be merged in the order you defined.
Let's running `nix repl -f '<nixpkgs>'` and then enter `:e lib.mkBefore` to take a look at its source code(To learn the basic usage of `nix repl`, just type `:?` to see the help information):
```nix
# ......
mkOrder = priority: content:
{ _type = "order";
inherit priority content;
};
mkBefore = mkOrder 500;
mkAfter = mkOrder 1500;
# The default priority for things that don't have a priority specified.
defaultPriority = 100;
# ......
```
So `lib.mkBefore` is a shortcut for `lib.mkOrder 500`, and `lib.mkAfter` is a shortcut for `lib.mkOrder 1500`.
To test the usage of `lib.mkBefore` and `lib.mkAfter`, let's create a simple Flake project:
```shell
# create flake.nix with the following content
cat <<EOF | sudo tee flake.nix
{
description = "Ryan's NixOS Flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# demo module 1, insert git at the head of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkBefore [pkgs.git];
})
# demo module 2, insert vim at the tail of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkAfter [pkgs.vim];
})
# demo module 3, just add curl to the list normally
({lib, pkgs, ...}: {
environment.systemPackages = with pkgs; [curl];
})
];
};
};
};
}
EOF
# create flake.lock
nix flake update
# enter nix repl environment
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# load the flake we just created
nix-repl> :lf .
Added 9 variables.
# check the order of systemPackages
nix-repl> outputs.nixosConfigurations.nixos-test.config.environment.systemPackages
[ «derivation /nix/store/0xvn7ssrwa0ax646gl4hwn8cpi05zl9j-git-2.40.1.drv»
«derivation /nix/store/7x8qmbvfai68sf73zq9szs5q78mc0kny-curl-8.1.1.drv»
«derivation /nix/store/bly81l03kh0dfly9ix2ysps6kyn1hrjl-nixos-container.drv»
......
......
«derivation /nix/store/qpmpvq5azka70lvamsca4g4sf55j8994-vim-9.0.1441.drv» ]
```
So we can see that the order of `systemPackages` is `git -> curl -> default packages -> vim`, which is the same as the order we defined in `flake.nix`.
> Though it's useless to adjust the order of `systemPackages`, it may be helpful at some other places...
### 8. Update the system
With Flakes, it is also very simple to update the system. Just run the following commands in `/etc/nixos`:
```shell
# update flake.lock
nix flake update
# apply the updates
sudo nixos-rebuild switch
```
Sometimes you may encounter some error of sha256 mismatch when running `nixos-rebuild switch`, which may be solved by updating `flake.lock` through `nix flake update`.
### 9. Downgrade or upgrade some packages
After using Flakes, most people are currently using the `nixos-unstable` branch of nixpkgs. Sometimes you will encounter some bugs, such as the [chrome/vscode crash problem](https://github.com/swaywm/sway/issues/7562)
To resolve problems, we may need to downgrade or upgrade some packages. In Flakes, all package versions and hash values are one-to-one corresponding to the git commit of their flake input.
Therefore, to downgrade or upgrade a package, we need to lock the git commit of its flake input.
For exmaple, let's add multiple nixpkgs, each using a different git commit or branch:
```nix
{
description = "NixOS configuration of Ryan Yin"
inputs = {
# default to nixos-unstable branch
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
# the latest stable branch of nixpkgs, used to rollback the version of some packages
# the current latest version is 22.11
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-22.11";
# we can also use git commit hash to lock the version
nixpkgs-fd40cef8d.url = "github:nixos/nixpkgs/fd40cef8d797670e203a27a91e4b8e6decf0b90c";
outputs = inputs@{
self,
nixpkgs,
nixpkgs-stable,
nixpkgs-fd40cef8d,
...
}: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
# The core parameter, which passes the non-default nixpkgs instances to other nix modules
specialArgs = {
# To use packages from nixpkgs-stable, we need to configure some parameters for it first
pkgs-stable = import nixpkgs-stable {
system = system; # refer the `system` parameter form outer scope recursively
# To use chrome, we need to allow the installation of non-free software
config.allowUnfree = true;
};
pkgs-fd40cef8d = import nixpkgs-fd40cef8d {
system = system;
config.allowUnfree = true;
};
};
modules = [
./hosts/nixos-test
# omit other configuration...
];
};
};
};
}
```
And then refer the packages from `pkgs-stable` or `pkgs-fd40cef8d` in your sub module, a home manager's sub module as an example:
```nix
{
pkgs,
config,
# nix will search and jnject this parameter from specialArgs in flake.nix
pkgs-stable,
# pkgs-fd40cef8d,
...
}:
{
# refer packages from pkgs-stable instead of pkgs
home.packages = with pkgs-stable; [
firefox-wayland
# chrome wayland support was broken on nixos-unstable branch, so fallback to stable branch for now
# https://github.com/swaywm/sway/issues/7562
google-chrome
];
programs.vscode = {
enable = true;
package = pkgs-stable.vscode; # refer vscode from pkgs-stable instead of pkgs
};
}
```
After adjusted the configuration, deploy it with `sudo nixos-rebuild switch`, then your firefox/chrome/vscode will be downgraded to the version corresponding to `nixpkgs-stable` or `nixpkgs-fd40cef8d`.
> according to [1000 instances of nixpkgs](https://discourse.nixos.org/t/1000-instances-of-nixpkgs/17347), it's not a good practice to use `import` in sub modules to customize `nixpkgs`, because each `import` will create a new instance of nixpkgs, which will increase the build time and memory usage as the configuration grows. So here we create all nixpkgs instances in `flake.nix` to avoid this problem.
### 10. Manage the configuration with Git
NixOS configuration is just a set of text files, it is very suitable to be managed with Git, and thus we can easily rollback to a previous version when we encounter some problems.
However, NixOS places the configuration in `/etc/nixos` by default, which requires root permissions to modify, which is not convenient for daily use.
Luckily, Flakes can solve this problem, you can place your flake anywhere you like.
For example, my usage is to place my flake in `~/nixos-config`, and then create a soft link in `/etc/nixos`:
```shell
sudo mv /etc/nixos /etc/nixos.bak # backup the original configuration
sudo ln -s ~/nixos-config/ /etc/nixos
# deploy the flake.nix located at the default location(/etc/nixos)
sudo nixos-rebuild switch
```
And then you can use Git to manage the configuration in `~/nixos-config`. The configuration can be used with ordinary user-level permissions, and it is not required to be owned by root.
Another method is jsut to delete `/etc/nixos` directly, and specify the configuration file path each time you deploy it:
```shell
sudo mv /etc/nixos /etc/nixos.bak
cd ~/nixos-config
# `--flake .#nixos-test` means deploy the flake.nix located in the current directory, and the nixosConfiguration's name is `nixos-test`
sudo nixos-rebuild switch --flake .#nixos-test
```
Choose whichever you like. After that, system rollback will become very simple, just switch to the previous commit and then deploy it:
```shell
cd ~/nixos-config
# switch to the previous commit
git checkout HEAD^1
# deploy the flake.nix located in the current directory, and the nixosConfiguration's name is `nixos-test`
sudo nixos-rebuild switch --flake .#nixos-test
```
More operations on Git are not described here. Generally speaking, rollback can be done directly through Git. Only when the system crashes completely, you will need to restart into bootloader and boot the system from the previous historical version.
### 11. View and delete history data {#view-and-delete-history}
As we mentioned before, each deployment of NixOS will generate a new version, and all versions will be added to the system boot options. In addition to restarting the computer, we can also query all available historical versions through the following command:
```shell
nix profile history --profile /nix/var/nix/profiles/system
```
The command to clean up historical versions to release storage space:
```shell
# delete all historical versions older than 7 days
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d
# we need to collect garbages after wipe-history
sudo nix store gc --debug
```
Another command returns all packages installed in the system:
```shell
nix-env -qa
```

309
docs/zh/nixpkgs/index.md Normal file
View file

@ -0,0 +1,309 @@
## VIII. Nixpkgs's Advanced Usage
`callPackage`, `Overriding`, and `Overlays` are the techniques occasionally used when using Nix to customize the build method of Nix packages.
We know that many programs have a large number of build parameters that need to be configured, and different users may want to use different build parameters. This is where `Overriding` and `Overlays` come in handy. Let me give you a few examples I have encountered:
1. [`fcitx5-rime.nix`](https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix): By default, `fcitx5-rime` use `rime-data` as the value of `rimeDataPkgs`, but this parameter can be customized by `override`.
2. [`vscode/with-extensions.nix`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/editors/vscode/with-extensions.nix): This package for VS Code can also be customized by overriding the value of `vscodeExtensions`, thus we can install some custom plugins into VS Code.
- [`nix-vscode-extensions`](https://github.com/nix-community/nix-vscode-extensions): This is a vscode plugin manager implemented by overriding `vscodeExtensions`.
3. [`firefox/common.nix`](https://github.com/NixOS/nixpkgs/blob/416ffcd08f1f16211130cd9571f74322e98ecef6/pkgs/applications/networking/browsers/firefox/common.nix): Firefox has many customizable parameters too.
4. ...
In short, `Overriding` or `Overlays` can be used to customize the build parameters of Nix packages.
### 1. pkgs.callPackage {#callpackage}
> [Chapter 13. Callpackage Design Pattern - Nix Pills](https://nixos.org/guides/nix-pills/callpackage-design-pattern.html)
In the previous content, We have used `import xxx.nix` to import Nix files many times, this syntax simply returns the execution result of the file, without any further processing of the it.
`pkgs.callPackage` is also used to import Nix files, its syntax is `pkgs.callPackage xxx.nix { ... }`, but unlike `import`, the Nix file imported by it must be a Derivation or a function that returns a Derivation. Its result is a Derivation(a software package) too.
So what does the Nix file that can be used as a parameter of `pkgs.callPackge` look like? You can take a look at the `hello.nix` `fcitx5-rime.nix` `vscode/with-extensions.nix` `firefox/common.nix` we mentioned earlier, they can all be imported by `pkgs.callPackage`.
When the `xxx.nix` used in `pkgs.callPackge xxx.nix {...}` is a function (most Nix packages are like this), the execution flow is as follows:
1. `pkgs.callPackge xxx.nix {...}` will first `import xxx.nix` to get the function defined in it. The parameters of this function usually have `lib`, `stdenv`, `fetchurl` and other parameters, as well as some custom parameters, which usually have default values.
2. Then `pkgs.callPackge` will first look up the value matching the name from the current environment as the parameter to be passed to the function. parameters like `lib` `stdenv` `fetchurl` are defined in nixpkgs, and they will be found in this step.
3. Then `pkgs.callPackge` will merge its second parameter `{...}` with the attribute set obtained in the previous step, and then pass it to the function imported from `xxx.nix` and execute it.
4. Finally we get a Derivation as the result of the function execution.
So the common usage of `pkgs.callPackage` is to import custom Nix packages and used it in Nix Module.
For example, we wrote a `hello.nix` ourselves, and then we can use `pkgs.callPackage ./hello.nix {}` in any Nix Module to import and use it.
### 2. Overriding {#overriding}
> [Chapter 4. Overriding - nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#chap-overrides)
Simply put, all Nix packages in nixpkgs can be customized with `<pkg>.override {}` to define some build parameters, which returns a new Derivation that uses custom parameters. For example:
```nix
pkgs.fcitx5-rime.override {rimeDataPkgs = [
./rime-data-flypy
];}
```
The result of this Nix expression is a new Derivation, where `rimeDataPkgs` is overridden as `[./rime-data-flypy]`, while other parameters remain their original values.
How to know which parameters of `fcitx5-rime` can be overridden? There are several ways:
1. Try to find the source code of the package in the nixpkgs repository on GitHub, such as [fcitx5-rime.nix](https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix)
1. Note: Be sure to select the correct branch, for example, if you are using the nixos-unstable branch, you need to find it in the nixos-unstable branch.
2. Check by using `nix repl '<nixpkgs>'`, then enter `:e pkgs.fcitx5-rime`, which will open the source code of this package through the default editor, and then you can see all the parameters of this package.
1. Note: To learn the basic usage of `nix repl`, just type `:?` to see the help information
Through these two methods, you can see that the `fcitx5-rime` package has the following input parameters, which can all be modified by `override`:
```nix
{ lib, stdenv
, fetchFromGitHub
, pkg-config
, cmake
, extra-cmake-modules
, gettext
, fcitx5
, librime
, rime-data
, symlinkJoin
, rimeDataPkgs ? [ rime-data ]
}:
stdenv.mkDerivation rec {
...
}
```
Instead of override the function's parameters, we can also override the attributes of the Derivation created by `stdenv.mkDerivation`.
Take `pkgs.hello` as an example, first check the source code of this package through the method we mentioned earlier:
```nix
# https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/applications/misc/hello/default.nix
{ callPackage
, lib
, stdenv
, fetchurl
, nixos
, testers
, hello
}:
stdenv.mkDerivation (finalAttrs: {
pname = "hello";
version = "2.12.1";
src = fetchurl {
url = "mirror://gnu/hello/hello-${finalAttrs.version}.tar.gz";
sha256 = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA=";
};
doCheck = true;
# ......
})
```
The attributes showed above, such as `pname` `version` `src` `doCheck`, can all be overridden by `overrideAttrs`, for example:
```nix
helloWithDebug = pkgs.hello.overrideAttrs (finalAttrs: previousAttrs: {
doCheck = false;
});
```
Here we use `overrideAttrs` to override `doCheck`, while other attributes remain their original values.
Some default attributes defined in `stdenv.mkDerivation` can also be overridden by `overrideAttrs`, for example:
```nix
helloWithDebug = pkgs.hello.overrideAttrs (finalAttrs: previousAttrs: {
separateDebugInfo = true;
});
```
The attribute we override here, `separateDebugInfo`, is defined in `stdenv.mkDerivation`, not in the source code of `hello`.
We can check the source code of `stdenv.mkDerivation` to see all the attributes defined in it by using `nix repl '<nixpkgs>'` and then enter `:e stdenv.mkDerivation`(To learn the basic usage of `nix repl`, just type `:?` to see the help information).
### 3. Overlays
> [Chapter 3. Overlays - nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#chap-overlays)
The `override` we introduced previously will generate a new Derivation, which does not affect the original Derivation in `pkgs`, and is only suitable for use as a local parameter,
if you need to override a Derivation that is also depended on by other Nix packages, then other Nix packages will still use the original Derivation.
To solve this problem, Nix provides the ability to use `overlays`. Simply put, `overlays` can globally modify the Derivation in `pkgs`.
In the classic Nix environment, Nix automatically applies all `overlays` configuration under the paths `~/.config/nixpkgs/overlays.nix` `~/.config/nixpkgs/overlays/*.nix`,
but in Flakes, in order to ensure the reproducibility of the system, it cannot depend on any configuration outside the Git repository, so this classic method cannot be used now.
When using Flakes to write configuration for NixOS, home Manager and NixOS both provide the `nixpkgs.overlays` option to define `overlays`, related documentation:
- [home-manager docs - `nixpkgs.overlays`](https://nix-community.github.io/home-manager/options.html#opt-nixpkgs.overlays)
- [nixpkgs source code - `nixpkgs.overlays`](https://github.com/NixOS/nixpkgs/blob/30d7dd7e7f2cba9c105a6906ae2c9ed419e02f17/nixos/modules/misc/nixpkgs.nix#L169)
For example, the following content is a Module that loads Overlays, which can be used as either a home Manager Module or a NixOS Module, because the two definitions are exactly the same:
> home Manager is an external component after all, and most people use the unstable branch of home Manager & nixpkgs, which sometimes causes problems with home Manager Module, so it is recommended to import `overlays` in a NixOS Module.
```nix
{ config, pkgs, lib, ... }:
{
nixpkgs.overlays = [
# overlayer1 - use self and super to express the inheritance relationship
(self: super: {
google-chrome = super.google-chrome.override {
commandLineArgs =
"--proxy-server='https=127.0.0.1:3128;http=127.0.0.1:3128'";
};
})
# overlayer2 - you can also use `extend` to inherit other overlays
# use `final` and `prev` to express the relationship between the new and the old
(final: prev: {
steam = prev.steam.override {
extraPkgs = pkgs:
with pkgs; [
keyutils
libkrb5
libpng
libpulseaudio
libvorbis
stdenv.cc.cc.lib
xorg.libXcursor
xorg.libXi
xorg.libXinerama
xorg.libXScrnSaver
];
extraProfile = "export GDK_SCALE=2";
};
})
# overlay3 - define overlays in other files
# here the content of overlay3.nix is the same as above:
# `final: prev: { xxx = prev.xxx.override { ... }; }`
(import ./overlays/overlay3.nix)
];
}
```
refer to this example to write your own overlays, import the configuration as a NixOS Module or a home Manager Module, and then deploy it to see the effect.
#### Modular overlays
The previous example shows how to write overlays, but all overlays are written in a single nix file, which is a bit difficult to maintain.
To resolve this problem,here is a best practice of how to manage overlays in a modular way.
First, create an `overlays` folder in the Git repository to store all overlays configuration, and then create `overlays/default.nix`, whose content is as follows:
```nix
args:
# import all nix files in the current folder, and execute them with args as parameters
# The return value is a list of all execution results, which is the list of overlays
builtins.map
(f: (import (./. + "/${f}") args)) # the first parameter of map, a function that import and execute a nix file
(builtins.filter # the second parameter of map, a list of all nix files in the current folder except default.nix
(f: f != "default.nix")
(builtins.attrNames (builtins.readDir ./.)))
```
Then you can write all overlays configuration in the `overlays` folder, an example configuration `overlays/fcitx5/default.nix` is as follows:
```nix
# to add my custom input method, I override the default rime-data here
# refer to https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix
{pkgs, config, lib, ...}:
(self: super: {
# my custom input method's rime-data, downloaded from https://flypy.com
rime-data = ./rime-data-flypy;
fcitx5-rime = super.fcitx5-rime.override { rimeDataPkgs = [ ./rime-data-flypy ]; };
})
```
I custom the `rime-data` package through the overlay shown above.
At last, you need to load all overlays returned by `overlays/default.nix` through the `nixpkgs.overlays` option, add the following parameter to any NixOS Module to achieve this:
```nix
{ config, pkgs, lib, ... } @ args:
{
# ......
# add this parameter
nixpkgs.overlays = import /path/to/overlays/dir;
# ......
}
```
For example, you can just add it directly in `flake.nix`:
```nix
{
description = "NixOS configuration of Ryan Yin";
# ......
inputs = {
# ......
};
outputs = inputs@{ self, nixpkgs, ... }: {
nixosConfigurations = {
nixos-test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = inputs;
modules = [
./hosts/nixos-test
# add the following inline module definition
# here, all parameters of modules are passed to overlays
(args: { nixpkgs.overlays = import ./overlays args; })
# ......
];
};
};
};
}
```
By using this modular approach, it is very convenient to modularize all your overlays. Taking my configuration as an example, the structure of the `overlays` folder is roughly as follows:
```nix
.
├── flake.lock
├── flake.nix
├── home
├── hosts
├── modules
├── ......
├── overlays
│ ├── default.nix # it returns a list of all overlays.
│ └── fcitx5 # fcitx5 overlay
│ ├── default.nix
│ ├── README.md
│ └── rime-data-flypy # my custom rime-data
│ └── share
│ └── rime-data
│ ├── ...... # rime-data files
└── README.md
```
## IV. Package Repositories of Nix
Similar to Arch Linux, Nix also has official and community software package repositories:
1. [nixpkgs](https://github.com/NixOS/nixpkgs) is a Git repository containing all Nix packages and NixOS modules.
1. Its `master` branch contains the latest Nix packages and modules.
2. The `nixos-unstable` branch contains the latest tested modules, but some bugs may still exist.
3. And the `nixos-XX.YY` branch(the stable branch) contains the latest stable Nix packages and modules.
2. [NUR](https://github.com/nix-community/NUR) is similar to Arch Linux's AUR.
1. NUR is a third-party Nix package repository and serves as a supplement to nixpkgs, use it at your own risk.
3. Flakes can also install software packages directly from Git repositories, which can be used to install Flakes provided by anyone, we will talk about this later.

View file

@ -0,0 +1,359 @@
Nix 语言是 Nix 包管理器的基础,要想玩得转 NixOS 与 Nix Flakes享受到它们带来的诸多好处就必须学会这门语言。
Nix 是一门比较简单的函数式语言,在已有一定编程基础的情况下,过一遍这些语法用时应该在 2 个小时以内,本文假设你具有一定编程基础(也就是说写得不会很细)。
这一节主要包含如下内容:
1. 数据类型
2. let...in... with inherit 等特殊语法
3. 函数的声明与调用语法
4. 内置函数与库函数
5. inputs 的不纯性Impurities
6. 用于描述 Build Task 的 Derivation
7. Overriding 与 Overlays
8. ...
先把语法过一遍,有个大概的印象就行,后面需要用到时再根据右侧目录回来复习。
## 1. 基础数据类型一览 {#basic-data-types}
下面通过一个 attribute set (这类似 json 或者其他语言中的 map/dict来简要说明所有基础数据类型
```nix
{
string = "hello";
integer = 1;
float = 3.141;
bool = true;
null = null;
list = [ 1 "two" false ];
attribute-set = {
a = "hello";
b = 2;
c = 2.718;
d = false;
}; # comments are supported
}
```
以及一些基础操作符(普通的算术运算、布尔运算就跳过不介绍了):
```nix
# 列表拼接
[ 1 2 3 ] ++ [ 4 5 6 ] # [ 1 2 3 4 5 6 ]
# 将 // 后面的 attribut set 中的内容,全部更新到 // 前面的 attribute set 中
{ a = 1; b = 2; } // { b = 3; c = 4; } # 结果为 { a = 1; b = 3; c = 4; }
# 逻辑隐含,等同于 !b1 || b2.
bool -> bool
```
## 2. let ... in ... {#let-in}
Nix 的 `let ... in ...` 语法被称作「let 表达式」或者「let 绑定」,它用于创建临时使用的局部变量:
```nix
let
a = 1;
in
a + a # 结果是 2
```
let 表达式中的变量只能在 `in` 之后的表达式中使用,理解成临时变量就行。
## 3. attribute set 说明 {#attribute-set}
花括号 `{}` 用于创建 attribute set也就是 key-value 对的集合,类似于 JSON 中的对象。
attribute set 默认不支持递归引用,如下内容会报错:
```nix
{
a = 1;
b = a + 1; # error: undefined variable 'a'
}
```
不过 Nix 提供了 `rec` 关键字recursive attribute set可用于创建递归引用的 attribute set
```nix
rec {
a = 1;
b = a + 1; # ok
}
```
在递归引用的情况下Nix 会按照声明的顺序进行求值,所以如果 `a``b` 之后声明,那么 `b` 会报错。
可以使用 `.` 操作符来访问 attribute set 的成员:
```nix
let
a = {
b = {
c = 1;
};
};
in
a.b.c # 结果是 1
```
`.` 操作符也可直接用于赋值:
```nix
{ a.b.c = 1; }
```
此外 attribute set 还支持一个 has attribute 操作符,它可用于检测 attribute set 中是否包含某个属性,返回 bool 值:
```nix
let
a = {
b = {
c = 1;
};
};
in
a?b # 结果是 true因为 a.b 这个属性确实存在
```
has attribute 操作符在 nixpkgs 库中常被用于检测处理 `args?system` 等参数,以 `(args?system)``(! args?system)` 的形式作为函数参数使用(叹号表示对 bool 值取反,是常见 bool 值运算符)。
## 4. with 语句 {#with-statement}
with 语句的语法如下:
```nix
with <attribute-set> ; <expression>
```
`with` 语句会将 `<attribute-set>` 中的所有成员添加到当前作用域中,这样在 `<expression>` 中就可以直接使用 `<attribute-set>` 中的成员了,简化 attribute set 的访问语法,比如:
```nix
let
a = {
x = 1;
y = 2;
z = 3;
};
in
with a; [ x y z ] # 结果是 [ 1 2 3 ], 等价于 [ a.x a.y a.z ]
```
## 5. 继承 inherit ... {#inherit}
`inherit` 语句用于从 attribute set 中继承成员,同样是一个简化代码的语法糖,比如:
```nix
let
x = 1;
y = 2;
in
{
inherit x y;
} # 结果是 { x = 1; y = 2; }
```
inherit 还能直接从某个 attribute set 中继承成员,语法为 `inherit (<attribute-set>) <member-name>;`,比如:
```nix
let
a = {
x = 1;
y = 2;
z = 3;
};
in
{
inherit (a) x y;
} # 结果是 { x = 1; y = 2; }
```
## 6. ${ ... } 字符串插值 {#string-interpolation}
`${ ... }` 用于字符串插值,懂点编程的应该都很容易理解这个,比如:
```nix
let
a = 1;
in
"the value of a is ${a}" # 结果是 "the value of a is 1"
```
## 7. 文件系统路径 {#file-system-path}
Nix 中不带引号的字符串会被解析为文件系统路径,路径的语法与 Unix 系统相同。
## 8. 搜索路径 {#search-path}
> 请不要使用这个功能,它会导致不可预期的行为。
Nix 会在看到 `<nixpkgs>` 这类三角括号语法时,会在 `NIX_PATH` 环境变量中指定的路径中搜索该路径。
因为环境变量 `NIX_PATH` 是可变更的值,所以这个功能是不纯的,会导致不可预期的行为。
在这里做个介绍,只是为了让你在看到别人使用类似的语法时不至于抓瞎。
## 9. 多行字符串 {#multi-line-string}
多行字符串的语法为 `''`,比如:
```nix
''
this is a
multi-line
string
''
```
## 10. 函数 {#nix-function}
函数的声明语法为:
```nix
<arg1>:
<body>
```
举几个常见的例子:
```nix
# 单参数函数
a: a + a
# 嵌套函数
a: b: a + b
# 双参数函数
{ a, b }: a + b
# 双参数函数,带默认值。问号后面的是参数的默认值
{ a ? 1, b ? 2 }: a + b
# 带有命名 attribute set 作为参数的函数,并且使用 ... 收集其他可选参数
# 命名 args 与 ... 可选参数通常被一起作为函数的参数定义使用
args@{ a, b, ... }: a + b + args.c
# 如下内容等价于上面的内容,
{ a, b, ... }@args: a + b + args.c
# 但是要注意命名参数仅绑定了输入的 attribute set默认参数不在其中举例
let
f = { a ? 1, b ? 2, ... }@args: args
in
f {} # 结果是 {},也就说明了 args 中包含默认值
# 函数的调用方式就是把参数放在后面,比如下面的 2 就是前面这个函数的参数
a: a + a 2 # 结果是 4
# 还可以给函数命名,不过必须使用 let 表达式
let
f = a: a + a;
in
f 2 # 结果是 4
```
### 内置函数 {#built-in-function}
Nix 内置了一些函数,可通过 `builtins.<function-name>` 来调用,比如:
```nix
builtins.add 1 2 # 结果是 3
```
详细的内置函数列表参见 [Built-in Functions - Nix Reference Mannual](https://nixos.org/manual/nix/stable/language/builtins.html)
### import 表达式 {#import-expression}
`import` 表达式以其他 Nix 文件的路径作为参数,返回该 Nix 文件的执行结果。
`import` 的参数如果为文件夹路径,那么会返回该文件夹下的 `default.nix` 文件的执行结果。
举个例子,首先创建一个 `file.nix` 文件:
```shell
$ echo "x: x + 1" > file.nix
```
然后使用 import 执行它:
```nix
import ./file.nix 1 # 结果是 2
```
### pkgs.lib 函数包 {#pkgs-lib}
除了 builtins 之外Nix 的 nixpkgs 仓库还提供了一个名为 `lib` 的 attribute set它包含了一些常用的函数它通常被以如下的形式被使用
```nix
let
pkgs = import <nixpkgs> {};
in
pkgs.lib.strings.toUpper "search paths considered harmful" # 结果是 "SEARCH PATHS CONSIDERED HARMFUL"
```
可以通过 [Nixpkgs Library Functions - Nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#sec-functions-library) 查看 lib 函数包的详细内容。
## 11. 不纯Impurities {#impurities}
Nix 语言本身是纯函数式的,是纯的,「纯」是指它就跟数学中的函数一样,同样的输入永远得到同样的输出。
Nix 有两种构建输入,一种是从文件系统路径等输入源中读取文件,另一种是将其他函数作为输入。
**Nix 唯一的不纯之处在这里:从文件系统路径或者其他输入源中读取文件作为构建任务的输入**这些输入源参数可能没变化但是文件内容或数据源的返回内容可能会变化这就会导致输入相同Nix 函数的输出却可能不同——函数变得不纯了。
> Nix 中的搜索路径与 `builtins.currentSystem` 也是不纯的,但是这两个功能都不建议使用,所以这里略过了。
## 12. Fetchers {#fetchers}
构建输入除了直接来自文件系统路径之外,还可以通过 Fetchers 来获取Fetcher 是一种特殊的函数,它的输入是一个 attribute set输出是 Nix Store 中的一个系统路径。
Nix 提供了四个内置的 Fetcher分别是
- `builtins.fetchurl`:从 url 中下载文件
- `builtins.fetchTarball`:从 url 中下载 tarball 文件
- `builtins.fetchGit`:从 git 仓库中下载文件
- `builtins.fetchClosure`:从 Nix Store 中获取 Derivation
举例:
```nix
builtins.fetchurl "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"
# result example => "/nix/store/7dhgs330clj36384akg86140fqkgh8zf-7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"
builtins.fetchTarball "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"
# result example(auto unzip the tarball) => "/nix/store/d59llm96vgis5fy231x6m7nrijs0ww36-source"
```
## 13. Derivations {#derivations}
一个构建动作的 Nix 语言描述被称做一个 Derivation它描述了如何构建一个软件包它的构建结果是一个 Store Object.
Store Object 的存放路径格式为 `/nix/store/<hash>-<name>`,其中 `<hash>` 是构建结果的 hash 值,`<name>` 是它的名字。路径 hash 值确保了每个构建结果都是唯一的,因此可以多版本共存,而且不会出现依赖冲突的问题。
`/nix/store` 被称为 Store存放所有的 Store Objects这个路径被设置为只读只有 Nix 本身才能修改这个路径下的内容,以保证系统的可复现性。
在 Nix 语言的最底层,一个构建任务就是使用 builtins 中的不纯函数 `derivation` 创建的,我们实际使用的 `stdenv.mkDerivation` 就是它的一个 wrapper屏蔽了底层的细节简化了用法。
## 14. if...then...else... {#if-then-else}
if...then...else... 用于条件判断,它是一个有返回值的表达式,语法如下:
```nix
if 3 > 4 then "yes" else "no" # 结果为 "no"
```
也可以与 let...in... 一起使用:
```nix
let
x = 3;
in
if x > 4 then "yes" else "no" # 结果为 "no"
```
## 参考
- [Nix language basics - nix.dev](https://nix.dev/tutorials/first-steps/nix-language)