在 Nix 中从 URL 导入
我还是个 Nix 新手,直到最近我才弄明白的一件事是,如何把 configuration.nix 文件的部分内容纳入版本控制。
我的目标
我希望我的 Nix 配置文件是模块化、可复用的,这样根据不同的系统或 flake,我可以只引入我需要的配置文件。我希望所有 Nix 配置文件都纳入版本控制,这样不同的系统可以依赖不同版本的任意文件,而不必同时把每个系统都升级到每个配置文件的最新版本。
用本地文件创建可复用的 bash 别名
我想复用的 Nix 配置之一是我的 bash 别名。在我现有的系统上,我的 .bashrc 中有这几行:
alias gc='git commit --message'
alias gs='git status'
alias td='pushd $(mktemp -d)'要在 bash 中实现等效效果,我可以在我的 NixOS 系统上创建一个包含这些内容的文件。我把它命名为 shell.nix,但其实叫什么都行:
{
programs.bash.shellAliases = {
gc = "git commit --message";
gs = "git status";
td = "pushd $(mktemp -d)";
};
}如果我把这个文件存放在 /tmp/shell.nix,那么我可以把它导入到我的 /etc/nixos/configuration.nix 中,方法是把路径加到 imports 里:
{
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 上,然后就可以用一个引用该 URL 的标准 configuration.nix 文件。
下面是我如何调整 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 仓库的公开 URL,而 rev 是我要导入的文件版本对应的 Git 提交哈希值。
对 configuration.nix 做出这些修改后,我可以重新运行 sudo nixos-rebuild switch,Nix 就会从我的 GitHub 仓库导入我的 shell.nix 文件。
更复杂的 shell 配置
我的 bash 别名大多是简单的单行命令,但有几个更复杂。对于这些,我会定义 bash 函数,然后再为这些函数创建简短的 bash 别名。
一个例子是我的 gcbm bash 别名。我这样使用它:gcbm some-branch,它会执行以下操作:
- 检出 main 分支。
- 从远程仓库拉取最新更改。
- 检出一个名为
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 bash 函数中某一行的方式:
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 文件。
随机一篇博客