Import from a URL in Nix

Michael Lynch

在 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,就可以透過將路徑加入 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 上,這樣我就能使用一個會參照該 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 來取得檔案。

以下是從公開的 GitHub 儲存庫取得我的 shell.nixconfiguration.nix 檔案範例:

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

url 是我的 GitHub 儲存庫的公開 URL,而 rev 是我想匯入的檔案版本的 Git commit hash。

configuration.nix 做出這些變更後,我可以重新執行 sudo nixos-rebuild switch,Nix 就會從我的 GitHub 儲存庫匯入 shell.nix 檔案。

更進階的 shell 設定

我的大多數 bash 別名都是簡單的一行指令,但有幾個比較複雜。對於這些情況,我會定義 bash 函式,然後為這些函式建立簡短的 bash 別名。

其中一個例子是我的 gcbm bash 別名。我會像這樣使用它:gcbm some-branch,它會執行以下動作:

  1. 切換到 main 分支。
  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 bash 函式中撰寫其中一行的方式:

git checkout "${MAIN_BRANCH}"

Nix 出現了以下錯誤:

error: undefined variable 'MAIN_BRANCH'

我需要在 ${ 前面加上兩個單引號('')來跳脫 ${,如下所示:

git checkout "''${MAIN_BRANCH}"

為什麼不用 Home Manager?

我想,用 Home Manager 來模組化你的 Nix 設定是更為主流的做法。

但老實說,我仍然不太理解 Home Manager 的用途。根據該專案的 README:

這個專案提供了一套基礎系統,可結合 Nix 套件管理器與 Nixpkgs 中的 Nix 函式庫來管理使用者環境。它允許以宣告式方式設定使用者專屬(非全域)的套件與 dotfiles。

這讓我感到困惑,因為聽起來這就是 Nix 本來就在做的事。

我已經看過 Home Manager 好幾次,但每次都覺得必須投入大量時間學習這個新工具,而能獲得的好處對我來說卻不明確。

或許我最終會體會到用 Home Manager 管理 Nix 設定的好處,但就目前而言,我已經找到一種相當直接的方式,可以用標準的 Nix 來管理遠端的 Nix 檔案。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯