Koala

NixOS 春节假期折腾之旅

春节在家刷到 ryan4yin 的博客,看完觉得 NixOS 挺有意思。折腾 Linux 这么久,/etc 下的配置文件改吐了,依赖冲突也踩了不少坑。系统一升级环境就炸,Docker 也只能临时救个急。

NixOS 让人强烈欲望入坑的特点

  • 用一份 /etc/nixos/configuration.nix 文件定义整个系统
  • 每次系统配置更新,都会提供 Generation 防止错误配置无法回退
  • 完全可复现的开发环境,只需要 git clone,不再需要 pacmanapt

可复现特性基础

NixOS 中 /nix/store 目录是只读的,这个目录存储了所有通过 Nix 包管理器安装的软件包、库文件和配置文件。每个包都有一个唯一的哈希值路径,例如 /nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/

只读设计保证了软件包不会被乱改,同一个包的不同版本可以共存,系统更新出问题了也能直接回滚。这就是 NixOS 能做到完全可复现的关键。

安装 NixOS

我们可以从 NixOS 官方下载 Minimal ISO image,在一台全新的机器上安装 NixOS。或是通过 nixos-infect 脚本,一键将非 NixOS 的系统转换成 NixOS。后者在阿里云 ECS 上测试是可用的。

非常非常 Easy 的配置文件 /etc/nixos/configuration.nix,在下面的配置中,我们指定了 root 用户的 authorizedKeys 以及密码,配置了 ssh-server,配置了 vscode-server,主机名等信息都可以通过该配置文件完成。当把下面的文件写入后,只需要运行 nixos-rebuild switch,一切都完成了。

我的服务器通过手动划分磁盘,挂载 btrfs 后,执行 nixos-install 脚本安装。更详细的安装教程可以参考这篇博客:从 0 实现全集梦中情 OS。如果对于操作没有那么熟悉,可以现在虚拟机中尝试,后续想部署到服务器上,只需要复制配置文件到服务器,执行 nixos-rebuild switch,就都一模一样了。

# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).

{ config, lib, pkgs, ... }:

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
      # vscode server
      (fetchTarball {
        url = "https://github.com/nix-community/nixos-vscode-server/tarball/master";
        sha256 = "09j4kvsxw1d5dvnhbsgih0icbrxqv90nzf0b589rb5z6gnzwjnqf";
      })
    ];

  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # Pick only one of the below networking options.
  # networking.wireless.enable = true;
  networking.networkmanager.enable = true;

  # 配置时区等机器信息
  time.timeZone = "Asia/Shanghai";
  i18n.defaultLocale = "en_US.UTF-8";
  networking.hostName = "hostname";

  nixpkgs.config.allowUnfree = true;

  # 安装的软件
  environment.systemPackages = with pkgs; [
    openssl
    wget
    curl
    nano
    tailscale
  ];
  # 设置默认编辑器
  environment.variables = {
    EDITOR = "nano";
  };

  # 配置 OpenSSH 和 Tailscale
  services.openssh.settings = {
    PermitRootLogin = "yes";
    PasswordAuthentication = true;
  };

  # 启动系统服务,类似 systemctl enable --now xxx
  services.openssh.enable=true;
  services.tailscale.enable = true;
  services.vscode-server.enable = true;

  # Root 用户的配置
  users.users.root = {
    openssh.authorizedKeys.keys = [
      "ssh-rsa AAAAB3NzaC......tTMFP5y8v8= therainisme@qq.com"
    ];
    # 这密码我随便写的
    password = "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865";
  };

  system.stateVersion = "24.11";
}

开发环境配置(Devbox 与 Direnv)

在软件开发中,环境配置一直是令人头疼的难题。不同项目对编译器版本、依赖库、环境变量的差异化需求,常常导致"在我的机器上能运行"的经典问题。接下来将介绍基于 Devbox 和 Direnv 管理环境方案,通过两个典型 Go 项目案例,展示如何实现开发环境的精准控制。

实话说,这两个如果不是 NixOS 也能用,而且比在 NixOS 上还好用。

准备事项

在所有例子之前,必须安装 devbox 和 direnv,同时给 IDE 装上支持 direnv 的插件。如果使用的是 NixOS,添加如下配置文件,并 nixos-rebuild switch

programs.direnv.enable = true;
environment.systemPackages = with pkgs; [
  ...
  devbox
];

例子一:杂交的 Go 程序,开启 CGO 与 Rocksdb@v9.8.4 版本

需要编译使用 CGO 的 Go 程序,并链接 RocksDB v9.8.4 数据库。该场景的复杂性在于:

  • 必须启用 CGO 支持
  • 需要特定版本的 C 库(RocksDB)
  • 依赖 pkg-config 查找头文件路径

首先进入项目的根目录,执行:

# 初始化 Devbox 项目
devbox init

# 添加精确版本依赖
devbox add rocksdb@9.8.4   # 指定 RocksDB 版本,不指定默认 @latest
devbox add pkg-config
devbox add gcc

# 生成 Direnv 配置
devbox generate direnv

此时打开 shell,输入 pkg-config --cflags --libs rocksdb 所有环境都已经配置好了,就这么简单。

$ pkg-config --cflags --libs rocksdb
-I/nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/include -L/nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/lib -lrocksdb

例子二:老旧的 Go 程序,必须使用 Go@1.20.3 版本

首先进入项目的根目录,执行:

devbox init
devbox add go@1.20.3  # 精确锁定 Go 版本
devbox generate direnv

此时打开 shell,执行以下步骤即可验证:

# 在项目目录内验证
$ go version
go version go1.20.3 linux/amd64

# 查看环境隔离效果
$ which go
/home/user/.devbox/nix/profile/default/bin/go

更多杂项配置

TODO

Nixpkg 相关资料

  1. https://search.nixos.org/options 搜索 /etc/nixos/configuration.nix 文件的配置写法,例如安装 docker,根据它的描述,只需要添加一行即可:virtualisation.docker.enable = true;
  2. https://search.nixos.org/packages 相关 nixpkgs 的搜索,只要包不是离奇的冷门,都能找到,不过找不到旧版本的包。比如说 Go 的 1.20 版本无法在 24.11 里找到。
  3. https://www.nixhub.io/ Devbox 的相关网站,可以找到最新的包和历史所有包。例如 Go 的 1.20 版本在这里

Devbox 常用命令

  • devbox init: 为项目初始化 devbox, 运行后项目目录下会出现 devbox.json 文件,可以直接在文件中添加要使用的软件包,也可以像 npm 之类的包管理器一样使用 devbox add 添加软件包。
  • devbox add: 添加软件包,后面可以跟有 @xxx 指定版本号,如 devbox add rocksdb@9.8.4
  • dev search: 搜索软件包
  • dev rm: 删除软件包
  • devbox generate direnv: 自动为 direnv 生成 .envrc 文件,安装配置好后会自动载入环境。
  • devbox shell: 手动进入环境终端
  • devbox run: 手动运行命令(在 devbox.jsonshell.scripts 字段下手动添加 script,类似 node 的 package.json 中的 scripts)