Import from a URL in Nix

Michael Lynch

在 Nix 中从 URL 导入

原文由 Michael Lynch 发布,订阅该博客

我还是个 Nix 新手,有一件事直到最近才弄明白,那就是如何把 configuration.nix 文件中的部分内容纳入版本控制。

我的目标

我希望 Nix 配置文件是模块化、可复用的,这样就可以根据不同的系统或 flake,只引入需要的配置文件。我还希望所有 Nix 配置文件都纳入版本控制,这样不同系统就可以依赖同一个文件的不同版本,而不必同时把每台系统上的每个配置文件都升级到最新版。

使用本地文件创建可复用的 bash 别名

我想复用的 Nix 配置之一是 bash 别名。在我现有的系统上,.bashrc 中有这样几行:

alias gc='git commit --message'
alias gs='git status'
alias td='pushd $(mktemp -d)'

要在 Nix 中实现同样的效果,我可以在 NixOS 系统上创建一个包含以下内容的文件。我把它命名为 shell.nix,不过叫什么都行:

{
  programs.bash.shellAliases = {
    gc = "git commit --message";
    gs = "git status";
    td = "pushd $(mktemp -d)";
  };
}

如果我把这个文件保存为 /tmp/shell.nix,就可以通过在 imports 中添加路径,将其导入到 /etc/nixos/configuration.nix 中:

{
  imports = [
    "/tmp/shell.nix"
  ];
}

如果我执行 sudo nixos-rebuild switch 然后重启 shell,就能看到 bash 别名已经生效:

$ td
/tmp/tmp.awb7PW7aus ~

[/tmp/tmp.awb7PW7aus]$ pwd
/tmp/tmp.awb7PW7aus

将 Nix 文件放到 URL 上

上面的方案可以工作,但需要在我的每台 Nix 系统上都复制一份相同的文件。

我更希望把这个文件托管在一个可公开访问的 URL 上,然后让标准的 configuration.nix 文件去引用这个 URL。

下面是我调整 configuration.nix 以从远程 URL 拉取我的 shell.nix 文件的方法:

let
  shellAliases = builtins.fetchurl {
    url = "https://mtlynch.io/notes/nix-import-from-url/shell.nix";
  };
in {
  imports = [
    shellAliases
  ];
}

同样,只要把这些改动保存到 configuration.nix,执行 sudo nixos-rebuild switch 并重启 shell,Nix 就会从该 URL 导入我的 bash 别名。

使用 fetchGit(可选)

获取远程 Nix 文件的另一种方式是将其存放在公开的 Git 仓库中,然后使用 fetchGit 来拉取文件。

下面是一个 configuration.nix 文件的示例,它从一个公开的 GitHub 仓库中拉取我的 shell.nix

let
  repo = builtins.fetchGit {
    url = "https://github.com/mtlynch/nix-files";
    rev = "f98500a995cb5838e40be139a8327867faaff2d5";
  };
in {
  imports = [
    "${repo}/shell.nix"
  ];
}

其中 url 是我的 GitHub 仓库的公开地址,rev 则是我想导入的文件版本所对应的 Git 提交哈希。

configuration.nix 做出这些修改后,再次执行 sudo nixos-rebuild switch,Nix 就会从我的 GitHub 仓库导入 shell.nix 文件。

更复杂的 Shell 配置

我的大多数 bash 别名都是简单的一行命令,但有几个要复杂一些。对于这些复杂的情况,我会先定义 bash 函数,再为这些函数创建简短的别名。

一个例子是我的 gcbm 别名。用法是这样的:gcbm some-branch,它会执行以下操作:

  1. 切换到主分支。
  2. 从远程仓库拉取最新改动。
  3. 创建一个名为 some-branch 的新分支并切换过去。

别名的核心逻辑由一个名为 git_sync_and_branch 的 bash 函数实现。下面是我在 shell.nix 中实现它的方式:

{
  programs.bash = {
    shellInit = ''
      function git_sync_and_branch {
        local readonly TARGET_BRANCH="''${1}"
        local readonly MAIN_BRANCH='master'

        git checkout "''${MAIN_BRANCH}" && \
          git pull origin "''${MAIN_BRANCH}" && \
          if [[ -n "''${TARGET_BRANCH}" ]]; then
            git checkout -b "''${TARGET_BRANCH}"
          fi
      }
      '';
    shellAliases = {
      gc = "git commit --message";
      gcbm = "git_sync_and_branch";
      gs = "git status";
      td = "pushd $(mktemp -d)";
    };
  };
}

坑点:转义美元符号

我在尝试把 bash 函数迁移到 Nix 时遇到的一个坑是,必须对 ${ 序列进行转义。否则 Nix 会尝试将其当作本地变量进行插值,但它们其实是 bash 变量,而不是 Nix 变量。

下面是我最初尝试在 git_sync_and_branch 函数中写的其中一行:

git checkout "${MAIN_BRANCH}"

Nix 报错如下:

error: undefined variable 'MAIN_BRANCH'

我需要通过在 ${ 前面加上两个单引号('')来转义 ${,像这样:

git checkout "''${MAIN_BRANCH}"

为什么不用 Home Manager?

我想,模块化 Nix 配置的更主流方式是用 Home Manager

但说实话,我还是不太明白 Home Manager 的作用。根据项目的 README:

该项目提供了一套基础系统,用于配合 Nix 包管理器及 Nixpkgs 中的 Nix 库来管理用户环境。它允许以声明式的方式配置用户专属(非全局)的软件包和 dotfiles。

这让我很困惑,因为听起来这就是 Nix 本身已经在做的事。

我也几次尝试去了解 Home Manager,但每次都感觉要投入大量精力去学习这个新工具,而它能带来的收益对我来说并不清晰。

也许将来我会意识到用 Home Manager 管理 Nix 配置的好处,但就目前而言,我已经找到了一种相当直接的方法,用原生 Nix 来管理远程 Nix 文件。

本文章由 muse-spark-1.2-contributor 进行翻译

评论