Nix 包管理实战

Nix 包管理器通过纯函数式方法实现了可复现、可回滚的软件包管理。本文详解 nixpkgs 仓库结构、derivation 原理、常用命令和社区生态。

1. Nix 包管理的核心理念

传统的包管理器(如 apt、yum、pacman)在全局文件系统上安装软件包,这种方式面临几个根本性问题:依赖冲突、难以回滚、构建不可复现。Nix 包管理器通过以下创新解决了这些问题:

  • 隔离安装:每个包安装在唯一的 /nix/store/<hash>-<name>-<version> 目录中,不同版本可以共存而互不影响
  • 纯函数式构建:软件包的构建过程被描述为一个纯函数,给定相同的输入总是产生相同的输出
  • 原子升级/回滚:因为包存储在独立的不可变路径中,升级和回滚只是切换符号链接,原子且安全
  • 多用户支持:多个用户可以同时安装不同版本的同一软件包而互不干扰

2. nixpkgs 包仓库

2.1 仓库结构

nixpkgs 是世界上最大的软件包仓库之一,包含超过 10 万个软件包。它的核心是一个庞大的 Nix 表达式集合,所有软件包的构建方式都用 Nix 语言描述。

# ~/.nix-defexpr/channels/nixpkgs 是默认的 nixpkgs 路径
# 其核心是 pkgs/top-level/all-packages.nix

{ pkgs ? import <nixpkgs> {} }:

pkgs.hello       # GNU Hello, 世界!
pkgs.git         # Git 版本控制
pkgs.vim         # Vim 编辑器
pkgs.nodejs_20   # Node.js 20
pkgs.rustc       # Rust 编译器
pkgs.go          # Go 编译器
pkgs.python311   # Python 3.11

2.2 频道(Channels)

频道是 nixpkgs 的发布渠道。更新频道相当于更新软件包索引:

# 列出已订阅的频道
nix-channel --list

# 添加官方频道
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs

# 更新频道(下载最新包定义)
nix-channel --update

# 移除频道
nix-channel --remove nixpkgs

常用频道:

频道用途更新频率
nixpkgs-unstable最新软件包每日
nixos-23.11NixOS 23.11 稳定版安全更新
nixos-24.05NixOS 24.05 稳定版安全更新
nixpkgs-darwinmacOS 专用与 unstable 同步

2.3 搜索软件包

# 搜索包(支持模糊匹配)
nix search nixpkgs nodejs
nix search nixpkgs "firefox"

# 查看包信息
nix eval nixpkgs#nodejs.version
nix eval nixpkgs#nodejs.meta.description
nix eval nixpkgs#nodejs.meta.license

3. Derivation:包的构建公式

3.1 Derivation 的基本概念

在 Nix 中,derivation 是软件包的构建公式。它描述了如何从源码(或其他构建产物)编译出一个软件包。

{ stdenv, fetchurl }:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchurl {
    url = "mirror://gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "sha256-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=";
  };

  # 构建依赖(只存在于构建过程,不出现在运行时)
  nativeBuildInputs = [ ];

  # 运行时依赖
  buildInputs = [ ];

  # configure 选项
  configureFlags = [ "--disable-nls" ];

  meta = with stdenv.lib; {
    description = "GNU Hello, 世界!程序";
    license = licenses.gpl3Plus;
    platforms = platforms.all;
  };
}

3.2 构建哈希

每个包的路径包含其 store path hash,这是对其构建过程所有输入(源码、依赖、构建脚本)的加密哈希。

/nix/store/1g1z6a0j0b7p3z7b3b2n1g0p3z7b3b2n-hello-2.12.1
          └────────────────────────────────┘
                        store path hash (base-32 编码)

这个哈希确保了:

  • 相同的源码和依赖 → 相同的路径 → 自动复用已有构建
  • 任何输入的改变 → 新的路径 → 新的构建

3.3 查看包的 store path

# 查看 hello 包的 store path
nix-build '<nixpkgs>' -A hello --no-out-link

# 查看包的依赖关系
nix-store --query --tree $(nix-build '<nixpkgs>' -A hello --no-out-link)

# 查看哪些包依赖于某个包
nix-store --query --referrers /nix/store/...-hello-2.12.1

4. 常用包管理命令

4.1 nix-env 命令行工具

# 安装包(到当前用户环境)
nix-env -iA nixpkgs.hello
nix-env -iA nixpkgs.git

# 列出已安装的包
nix-env -q
nix-env -q --out-path    # 显示 store path

# 卸载包
nix-env -e hello

# 升级所有包
nix-env -u
nix-env -u hello         # 升级指定包

# 回滚到上一个环境
nix-env --rollback

# 列出历史环境(用于回滚)
nix-env --list-generations

# 切换到指定版本
nix-env --switch-generation 42

4.2 临时使用包

# nix-shell:进入包含指定包的临时 shell
nix-shell -p python39 nodejs git

# 在临时环境中运行命令(不进入交互式 shell)
nix-shell -p jq --run "jq --version"

# 使用特定版本的包
nix-shell -p python39 python310 python311

4.3 构建包

# 从 nixpkgs 构建包
nix-build '<nixpkgs>' -A hello

# 构建当前目录的 default.nix
nix-build

# 构建并创建结果链接
nix-build -o my-hello '<nixpkgs>' -A hello

# 使用 Flakes 构建
nix build nixpkgs#hello

4.4 清理垃圾

# 删除未被任何环境引用的包
nix-collect-garbage

# 删除超过 30 天的旧版本
nix-collect-garbage --delete-older-than 30d

# 查看 store 大小
nix store info

# 优化 store(硬链接重复文件)
nix store optimise

5. 自定义包

5.1 覆盖现有包

使用 config.packageOverrides 修改现有包的属性:

# ~/.config/nixpkgs/config.nix
{
  packageOverrides = pkgs: {
    # 自定义包的 configure flags
    nginx = pkgs.nginx.override {
      modules = with pkgs.nginxModules; [ rtmp dav ];
    };

    # 使用特定版本的源码
    my-vim = pkgs.vim.overrideAttrs (oldAttrs: {
      version = "9.1.0-custom";
      src = pkgs.fetchFromGitHub {
        owner = "vim";
        repo = "vim";
        rev = "v9.1.0";
        sha256 = "sha256-xxxxxxxxxx=";
      };
    });
  };
}

5.2 创建私有包

# my-package.nix
{ pkgs ? import <nixpkgs> {} }:

pkgs.stdenv.mkDerivation {
  pname = "my-app";
  version = "0.1.0";

  src = pkgs.fetchgit {
    url = "https://github.com/example/my-app.git";
    rev = "abc123";
    sha256 = "sha256-xxxxxxxxxx=";
  };

  buildInputs = with pkgs; [ nodejs yarn ];

  buildPhase = ''
    yarn install --frozen-lockfile
    yarn build
  '';

  installPhase = ''
    mkdir -p $out/bin
    cp -r dist $out/
    makeWrapper ${pkgs.nodejs}/bin/node $out/bin/my-app \
      --add-flags "$out/dist/index.js"
  '';

  meta = with pkgs.lib; {
    description = "我的应用";
    license = licenses.mit;
  };
}

6. 多用户与权限

Nix 支持多用户安装模式,每个用户有自己的 profile:

/root/.nix-profile        # root 用户的包
/home/alice/.nix-profile  # alice 用户的包
/home/bob/.nix-profile    # bob 用户的包

系统级的包安装在 /nix/var/nix/profiles/default,对系统上的所有用户可用。

# 系统级安装(需要 root)
nix-env -iA nixpkgs.nginx --profile /nix/var/nix/profiles/default

# 用户级安装(默认)
nix-env -iA nixpkgs.vim

7. 总结

Nix 包管理的核心优势在于:

特性说明对比传统包管理器
隔离安装每个版本独立目录传统:全局覆盖
可复现构建相同输入 → 相同输出传统:构建环境不固定
原子升级符号链接切换,失败可回滚传统:中途失败难以恢复
多版本共存不同版本互不干扰传统:只能安装一个版本
依赖追踪精确的依赖树,无幽灵依赖传统:容易产生依赖缺失

掌握 Nix 包管理是进入 Nix 生态的第一步。接下来的文章将深入讲解如何使用 Nix 构建可复现的开发环境、配置 NixOS 系统以及利用 Flakes 管理项目。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「devops」更多文章

  1. 值班工程与告警疲劳治理
  2. 基础设施代码测试:Terratest、Kitchen 与 InSpec
  3. 发布列车与版本节奏治理