Files
nixos-and-flakes-book/docs/zh/best-practices/index.md
2023-06-23 21:19:20 +08:00

346 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 十一、最佳实践 {#best-practices}
> [Tips&Tricks for NixOS Desktop - NixOS Discourse][Tips&Tricks for NixOS Desktop - NixOS Discourse]
Nix 非常强大且灵活,做一件事有非常多的方法,这就导致了很难找到最合适的方法来做你的工作。
这里记录了一些我在使用 NixOS 中学习到的最佳实践,希望能帮到你。
### 1. 运行非 NixOS 的二进制文件 {#run-non-nixos-binaries}
NixOS 不遵循 FHS 标准,因此你从网上下载的二进制程序在 NixOS 上大概率是跑不了的。
为了在 NixOS 上跑这些非 NixOS 的二进制程序,需要做一些骚操作。有位老兄在这里总结了 10 种实现此目的的方法:[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),推荐一读。
我个人用的比较多的方法是,直接创建一个 FHS 环境来运行二进制程序,这种方法非常方便易用。
大概玩法是这样的,首先在你的 `environment.systemPackages` 中添加这个包:
```nix
{ config, pkgs, lib, ... }:
{
# ......
environment.systemPackages = with pkgs; [
# ......o
# 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 只提供一个最小的 FHS 环境,缺少很多常用软件所必须的基础包
# 所以直接使用它很可能会报错
#
# pkgs.appimageTools 提供了大多数程序常用的基础包,所以我们可以直接用它来补充
(pkgs.appimageTools.defaultFhsEnvArgs.targetPkgs pkgs) ++ with pkgs; [
pkg-config
ncurses
# 如果你的 FHS 程序还有其他依赖,把它们添加在这里
]
);
profile = "export FHS=1";
runScript = "bash";
extraOutputsToInstall = ["dev"];
}))
];
# ......
}
```
部署好上面的配置后,你就能用 `fhs` 命令进入我们定义好的 FHS 环境了,然后就可以运行你下载的二进制程序了,比如:
```shell
# 进入我们定义好的 fhs 环境,它就跟其他 Linux 发行版一样了
$ fhs
# 看看我们的 /usr/bin 里是不是多了很多东西
(fhs) $ ls /usr/bin
# 尝试下跑一个非 nixos 的二进制程序
(fhs) $ ./bin/code
```
### 2. 通过 `nix repl` 查看源码、调试配置 {#view-source-code-via-nix-repl}
前面我们已经使用 `nix repl '<nixpkgs>'` 看过很多次源码了,这是一个非常强大的工具,可以帮助我们理解 Nix 的工作原理。
要学会用 `nix repl`,最好先看看它的 help 信息:
```
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
```
我最常用的命令是 `:lf <ref>``:e <expr>`.
`:e <expr>` 非常直观,所以这里不再赘述,我们来看看 `:lf <ref>`
```nix
# 进入我的 nix 配置目录(建议替换成你自己的配置目录)
cd ~/nix-config/
# 进入 nix repl 解释器
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# 将我的 nix 配置作为一个 flake 加载到当前作用域中
nix-repl> :lf .
Added 16 variables.
# 按 <TAB> 看看当前作用域中有哪些变量,果然 nixosConfigurations inputs outputs 跟 packages 都在里面
# 这意味着我们可以很方便地检查这些配置的内部状态
nix-repl><TAB>
# ......omit some outputs
__isInt nixosConfigurations
__isList null
__isPath outPath
__isString outputs
__langVersion packages
# ......omit some outputs
# 看看 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
# 看看 inputs.nil.packages 里都有些啥
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
# 看看 outputs 里都有些啥
nix-repl> outputs.nixosConfigurations.<TAB>
outputs.nixosConfigurations.ai
outputs.nixosConfigurations.aquamarine
outputs.nixosConfigurations.kana
outputs.nixosConfigurations.ruby
# 看看 ai 的配置都有些啥
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
# 看看 `TERM` 这个环境变量的值是啥
nix-repl> outputs.nixosConfigurations.ai.config.home-manager.users.ryan.home.sessionVariables.TERM
"xterm-256color"
# 看下我使用 `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
#......
```
能看到,通过 `nix repl` 加载好我的 flake 配置后,就能很方便地检查所有的配置项了,这对于调试非常有用。
### 3. 远程部署
社区的一些工具,比如 [NixOps](https://github.com/NixOS/nixops), [deploy-rs](https://github.com/serokell/deploy-rs), 跟 [colmena](https://github.com/zhaofengli/colmena),都可以用来部署 NixOS 配置到远程主机,但是都太复杂了,所以先全部跳过。
实际上我们前面使用了多次的 NixOS 本机部署命令 `nixos-rebuild`,这个工具也支持通过 ssh 协议进行远程部署,非常方便。
美中不足的是,`nixos-rebuild` 不支持使用密码认证进行部署,所以要想使用它进行远程部署,我们需要:
1. 为远程主机配置 ssh 公钥认证。
2. 为了避免 sudo 密码认证失败,需要使用 `root` 用户进行部署,或者给用户授予 sudo 权限不需要密码认证。
1. 相关 issue: <https://github.com/NixOS/nixpkgs/issues/118655>
在搞定上面两点后,我们就可以使用 `nixos-rebuild` 进行远程部署了:
```bash
# 1. 首先将本地的 ssh 私钥加载到 ssh-agent 中,以便后续使用
ssh-add ~/.ssh/ai-idols
# 2. 将 NixOS 配置部署到远程主机,使用第一步添加的 ssh key 进行认证,用户名默认为 `$USER`
nixos-rebuild --flake .#aquamarine --target-host 192.168.4.1 --build-host 192.168.4.1 switch --use-remote-sudo --verbose
```
上面的命令会将 NixOS 配置部署到 `aquamarine` 这台主机上,参数解释如下:
1. `--target-host`: 设置远程主机的 ip 地址
2. `--build-host` 指定了构建 NixOS 配置的主机,这里设置为跟 `--target-host` 相同,表示在远程主机上构建配置。
3. `--use-remote-sudo` 表示部署需要用到远程主机的 sudo 权限,如果不设置这个参数,部署会失败。
如果你希望在本地构建配置,然后再部署到远程主机,可以命令中的 `--build-host aquamarinr` 替换为 `--build-host localhost`
另外如果你不想直接使用 ip 地址,可以在 `~/.ssh/config` 或者 `/etc/ssh/ssh_config` 中定义一些主机别名,比如:
> 当然如下这份配置也完全可以通过 Nix 来管理,这个就留给读者自己去实现了。
```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
```
这样我们就可以使用主机别名进行部署了:
```bash
nixos-rebuild --flake .#aquamarine --target-host aquamarine --build-host aquamarine switch --use-remote-sudo --verbose
```
### 4. 使用 Makefile 简化命令
> 注意: Makefile 的 target 名称不能与当前目录下的文件或者目录重名,否则 target 将不会被执行!
在使用 NixOS 的过程中,我们会经常使用 `nixos-rebuild` 命令,经常需要输入一大堆参数,比较繁琐。
所以我使用 Makefile 来管理我的 flake 配置相关的命令,简化使用。
我的 Makefile 大概内容截取如下:
```makefile
############################################################################
#
# 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
```
将上述 Makefile 放到 NixOS 配置的根目录下,然后我们就可以使用 `make` 命令来执行相关的命令了。
比如说我这里 `make deploy` 就是部署 NixOS 配置到本地主机,`make idols` 就是部署到我的远程主机集群。