NixOS 上手记录

在 jq 同学的一再安利下,我终于也入坑 NixOS 了)))

什么是 NixOS

NixOS 是独立开发的 GNU/Linux 发行,它旨在改进系统配置管理的现状。在 NixOS 中,整个操作系统,包括内核、应用程序、系统软件包、配置文件,统统都由Nix包管理器来创建。Nix 将所有软件包以彼此分离的方式进行存储,因此就不存在/bin/sbin/lib/usr之类的目录,而是保存在/nix/store中。NixOS 的其他创新特色包括可靠升级、回滚、可重现的系统配置、二进制代码基于源文件的管理模型、多用户包管理。

对于我来说,用 NixOS 的好处就是:

  1. 体验一把用程序写配置文件(?)
  2. 方便进行 VPS 的迁移和一键部署,使用 NixOS 只需要根据配置代码进行生成部署即可。
  3. 可以复制粘贴 jq 写的配置代码

安装 NixOS 至 ESXi 中

虽然可以直接使用其它发行版的 Linux 到 NixOS 的转换脚本,但是我的 ESXi 里也没装正经 Linux,所以不如直接全新安装。

  1. 下载 NixOS Minimal ISO image (64bit),上传到 ESXi 的 datastore 里。我下载的是21.11版本。
  2. 在 ESXi 中创建一个新的虚拟机,CD/DVD Drive 选择刚才 NixOS ISO 镜像,Boot Options 选择 BIOS(省事),并配置网络,开启虚拟机。
  3. 切换为root用户
    1
    sudo -i
  4. 检查网络配置。ESXi 会虚拟一个有线网络,使用ifconfig命令查看是否拿到 IP 地址。
  5. 创建分区表、格式化分区、挂载,创建一个 19G 的基本分区和一个 1G 的 Swap 分区。
    1
    2
    3
    4
    5
    6
    7
    [root@nixos:~]# parted /dev/sda -- mklabel msdos
    [root@nixos:~]# parted /dev/sda -- mkpart primary 1MiB -19GiB
    [root@nixos:~]# parted /dev/sda -- mkpart primary linux-swap -1GiB 100%
    [root@nixos:~]# mkfs.ext4 -L nixos /dev/sda1
    [root@nixos:~]# mkswap -L swap /dev/sda2
    [root@nixos:~]# mount /dev/disk/by-label/nixos /mnt
    [root@nixos:~]# swapon /dev/sda2
  6. 如果需要配置静态IP,使用如下命令。
    1
    2
    3
    4
    5
    6
    [root@nixos:~]# ifconfig <interface> <my-static-ip> netmask <my-netmask>
    [root@nixos:~]# ifconfig <interface> down
    [root@nixos:~]# ifconfig <interface> up
    [root@nixos:~]# route del -net 0.0.0.0
    [root@nixos:~]# route add default gw <gateway-ip>
    [root@nixos:~]# echo "nameserver 8.8.8.8" >> /etc/resolv.conf
  7. 开始安装。在安装的最后一步,会提示设置 root 密码。
    1
    2
    3
    [root@nixos:~]# nixos-generate-config --root /mnt
    [root@nixos:~]# sed -i 's#\# boot.loader.grub.device = "/dev/sda"#boot.loader.grub.device = "/dev/sda"#g' /mnt/etc/nixos/configuration.nix
    [root@nixos:~]# cd /mnt/etc/nixos/ && nixos-install
  8. 重启虚拟机(断开 CD/DVD Drive),完成安装

    如需在 GPT 磁盘中使用 UEFI 引导安装,或者解锁各种花里胡哨的安装方法,请参考文档

Nix 基本语法

官方文档介绍

举例 描述
基本类型
"Hello world" 字符串
"${pkgs.bash}/bin/sh" 包含表达式的字符串(展开为"/nix/store/hash-bash-version/bin/sh")
true, false 布尔类型
123 整数
./foo.png 路径
复合类型
{ x = 1; y = 2; } 集合,有 x 和 y 两个属性
{ foo.bar = 1; } 嵌套集合,等价于{ foo = { bar = 1; }; }
rec { x = "foo"; y = x + "bar"; } 集合中可以使用运算,等价于{ x = "foo"; y = "foobar"; }
[ "foo" "bar" ] 列表,有两个元素
运算符
"foo" + "bar" 字符串连接
1 + 2 整数相加
"foo" == "f" + "oo" 判断相等
"foo" != "bar" 判断不相等
!true 逻辑取反
{ x = 1; y = 2; }.x 选取属性
{ x = 1; y = 2; }.z or 3 选取属性,缺省值为3
{ x = 1; y = 2; } // { z = 3; } 集合合并(右侧会覆盖左侧)
控制结构
if 1 + 1 == 2 then "yes!" else "no!" 条件表达式
assert 1 + 1 == 2; "yes!" 断言检查,如果断言成立,则返回分号后的内容
let x = "foo"; y = "bar"; in x + y 变量定义
with pkgs.lib; head [ 1 2 3 ] 将给定集合中的所有属性添加到当前域
函数 (lambdas)
x: x + 1 传入整数x,返回x+1的函数
(x: x + 1) 100 函数调用,传入x=100,返回101
let inc = x: x + 1; in inc (inc (inc 100)) 函数绑定到变量名,并嵌套使用
{ x, y }: x + y 传入一个仅包含属性xy的集合,返回x+y的函数
{ x, y ? "bar" }: x + y 传入一个仅包含属性xy的集合(y的缺省值为"bar"),返回x+y的函数
{ x, y, ... }: x + y 传入一个包含属性xy的集合(其他属性被忽略),返回x+y的函数
{ x, y } @ args: x + y 传入一个仅包含属性xy的集合(绑定集合至变量args),返回x+y的函数
内置函数
import ./foo.nix 加载和返回 Nix 文件
map (x: x + x) [ 1 2 3 ] 将函数应用到列表中的每一个元素 (返回值为 [ 2 4 6 ])

开启 SSH 访问

通过 ESXi 的 Console 进行访问也不方便,毕竟 NixOS 也是 Linux,为什么不用简单好用的 SSH 来访问呢?其实 NixOS 自带了 SSH,但自动生成的配置也没有开启 SSH 访问,需要手动修改 configuration.nix 来开启 SSH 访问功能。

1
2
[root@nixos:~]# cd /etc/nixos
[root@nixos:/etc/nixos]# nano configuration.nix

其中有一行
1
# services.openssh.enable = true

将其改为
1
2
services.openssh.enable = true
services.openssh.permitRootLogin = "yes";

保存文件。然后使用以下命令重新构建系统。
1
[root@nixos:/etc/nixos]# nixos-rebuild switch 

关于nixos-rebuild命令的一些常见用法如下:

命令 描述
nixos-rebuild switch 根据新配置构建系统,使其成为 GRUB 启动时默认 profile,并尝试在当前系统中应用新的配置
nixos-rebuild switch -p test 根据新配置构建系统,创建一个新的 profile,在 GRUB 启动页面显示为NixOS - Profile 'test'
nixos-rebuild test 根据新配置构建系统,并尝试在当前系统中应用新的配置,但不使其成为 GRUB 启动时默认 profile
nixos-rebuild boot 根据新配置构建系统,使其成为 GRUB 启动时默认 profile,但不并尝试在当前系统中应用新的配置
nixos-rebuild build 根据新配置构建系统,但什么也不做,通常用来看是否编译正常

现在,可以使用ssh root@[虚拟机的IP]命令,输入密码进行登录。

但是,如果我想使用证书方式登录,应该怎么配置呢?首先,得找个地方存储我们的authorized_keys

1
2
3
[root@nixos:~]# cd /etc/nixos
[root@nixos:/etc/nixos]# mkdir -p ssh
[root@nixos:/etc/nixos]# echo 'ssh-rsa AAAAB3NzaC...E= jogle@home' > ssh/authorized_keys

然后修改configuration.nix文件,添加以下内容
1
2
3
users.users."root".openssh.authorizedKeys.keyFiles = [
/etc/nixos/ssh/authorized_keys
];

最后当然是重新构建系统。
1
[root@nixos:/etc/nixos]# nixos-rebuild switch 

如果证书配置正确的话,重新使用ssh root@[虚拟机的IP]命令进行连接即可直接登录。

参考链接:NixOS Wiki - SSH public key authentication

配置静态 IP

有时候需要配置静态 IP,要不然没有网络连接,甚至都没法进行 nixos-rebuild。配置静态 IP 仅需要编辑 configuration.nix

1
2
3
4
5
6
networking.interfaces.<interface>.ipv4.addresses = [ {
address = "192.168.1.2";
prefixLength = 24;
} ];
networking.defaultGateway = "192.168.1.1";
networking.nameservers = [ "8.8.8.8" ];

安装 Vim

系统里只自带了一个 Nano 文本编辑器,为了方便以后的 Nix 配置修改,所以我要安装一个 Vim。没有 Vim 用我快要死了)

configuration.nix 中可以找到:

1
2
3
4
5
6
7
# List packages installed in system profile. To search, run:
# $ nix search wget
# environment.systemPackages = with pkgs; [
# vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
# wget
# firefox
# ];

可以在environment.systemPackages中声明要安装的包名,NixOS 就会自动为我们安装。这种方式称为声明式包管理(Declarative Package Management)。就像 apt-get 一样简单)

所以我们顺手把environment.systemPackages改成

1
2
3
environment.systemPackages = with pkgs; [
nano vim wget curl
];

执行nixos-rebuild switch后就可以在命令行里使用 Vim 了。

装完了 Vim,自然少不了配置.vimrc,以及配置插件,那么如何配置呢?这里给出一种方法:

/etc/nixos/下创建一个vim.nix文件,内容填写为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with import <nixpkgs> {};

vim_configurable.customize {
# Specifies the vim binary name.
# E.g. set this to "my-vim" and you need to type "my-vim" to open this vim
# This allows to have multiple vim packages installed (e.g. with a different set of plugins)
name = "vim";
vimrcConfig.customRC = ''
syntax on
set nu!
set autoindent
'';
vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
# loaded on launch
start = [ vim-airline fugitive ];
# manually loadable by calling `:packadd $plugin-name`
};
}

然后在configuration.nix中,修改刚才的environment.systemPackages

1
2
3
environment.systemPackages = with pkgs; [
nano (import ./vim.nix) wget curl
];

使用 Fish Shell

configuration.nix 中添加一行,为 root 修改默认 shell 为 fish。

1
users.users.root.shell = pkgs.fish;

执行nixos-rebuild switch后,还是使用默认的 bash shell。这是因为nixos-rebuild命令不会自动开启/关闭用户服务,而是为每一个开启了用户服务的用户执行daemon-reload。所以,nixos-rebuild无法直接打开一个新的 fish shell 来替换当前使用的 bash shell。

解决方法是:重启!

关于 fish shell 的插件配置,可以参考 NixOS Wiki - fish

开启 Flakes

在刚才的配置中,我们只配置了软件包开启,但是没有指定软件包的版本。为了能对软件包的版本进行控制,需要开启 Nix Flakes。由于官方担心 Nix 2.4 功能变化过大,尤其是会与旧版 Nix 的行为不兼容,NixOS 21.11 仍使用 Nix 2.3,且默认禁用 Flake 功能,所以需要手动进行开启。

configuration.nix 中添加一下内容:

1
2
3
4
5
6
7
8
9
10
11
12
{ config, pkgs, ... }:

{
# ...
nix = {
package = pkgs.nixUnstable;
extraOptions = ''
experimental-features = nix-command flakes
'';
};
# ...
}

修改之后需要运行 nixos-rebuild switch 命令,将 Nix 包管理器升级到支持 Flake 的测试版。

然后在 /etc/nixos 里创建一个 flake.nix 文件。其中定义了一个软件源(input),是 NixPkgs 的 unstable 分支(也就是 master 分支)。文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
# 输入配置,即软件源
inputs = {
# Nixpkgs,即 NixOS 官方软件源
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};

# 输出配置,即 NixOS 系统配置
outputs = { self, nixpkgs, ... }@inputs: {
# 定义一个名为 nixos 的系统
nixosConfigurations."nixos" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
];
};
};
}

运行 nix flake update,会生成一个 flake.lock 文件。在下一次运行 nixos-rebuild switch 命令时,NixOS 会自动优先读取 flake.nix 而非 configuration.nix,把系统里的所有软件包升级(或降级)到这个特定的版本。但因为我们已经把 configuration.nix 加入至 flake.nix 中,所以系统配置还是保持不变。

远程部署

有些软件包在安装的时候需要编译,在低配置机器上执行 nixos-rebuild 会编译失败而出错。Flakes 支持远程部署,即先在本地进行编译,生成二进制文件,然后传输到目标机器上。仅需要在 flakes.nix 文件中增加相关的连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

# 新增下面几行
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations."nixos" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
];
};

# 新增下面几行
deploy = {
sshUser = "root"; # SSH 登录用户名
user = "root"; # 远程操作的用户
sshOpts = [ "-p" "2222" ]; # SSH 参数,这里是指定端口 2222

# 部署失败自动回滚,建议关闭
# 因为 NixOS(尤其是 Unstable 分支)部署不太稳定,有时需要部署两次才成功
# 如果自动回滚了,反而适得其反,导致连续部署失败
autoRollback = false;

# 断网自动回滚,建议关闭
# 在你配置防火墙或 IP 出错把网络干掉时,自动回滚,这样你就不用去主机商控制面板连 VNC 或 IPMI 了
# 但如果你就是在调整防火墙或者 IP 配置,会有当时断网、但重启机器就可以应用新配置恢复正常的情况
# 自动回滚反而适得其反,因此建议关闭
magicRollback = false;

nodes = {
"nixos" = {
# 目标机器的地址,IP 或域名或 .ssh/config 中配置的别名均可
hostname = "192.168.56.105";
profiles.system = {
# 调用上面的 nixosConfigurations."nixos"
path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations."nixos";
};
};
};
};
};
}

参考资料:https://lantian.pub/article/modify-website/nixos-initial-config-flake-deploy.lantian/