• オレオレNURリポジトリを構築する


    オレオレNURを作ったのでその方法を書いていく。

    なぜNURリポジトリを自作するのか

    Nixはflakeを使うことで自作したパッケージをリポジトリ単位で公開できる。 ただ、自作パッケージが増えていくとリポジトリが増えていく。

    そこで、それらのリポジトリを1つに集約することで各リポジトリでやっていた形式的な作業を省略して、あとあとの管理も簡単にできる。

    nur-packages-templateを使ってリポジトリを作成する

    nur-packagesを作るためのテンプレートリポジトリがある。 それを使ってリポジトリを作成していく。

    GitHubのテンプレート機能を使うと楽に作成できるのでお勧め。

    作成されたリポジトリはこんな感じの構造になっている。

    .
    ├── ci.nix
    ├── default.nix
    ├── flake.lock
    ├── flake.nix
    ├── lib
    ├── modules
    ├── overlay.nix
    ├── overlays
    └─ pkgs
    

    各ディレクトリの説明簡単にしていく。基本はnixpkgsと似たような感じになっている。

    • lib ライブラリ関数があるディレクトリ。
    • modules NixOS modulesがあるディレクトリ。
    • overlays Overlayがあるディレクトリ。
    • pkgs パッケージがあるディレクトリ。

    独自にパッケージしたflakeを移したいなら基本はpkgsを使うことになると思う。

    pkgsにパッケージを追加する方法

    pkgs配下にディレクトリを作成する。 このディレクトリ名は(おそらく)パッケージ名に影響しないので、好きにして良いと思う。 ただ、分かりやすさのために同じにした方が良い。

    ディレクトリを作成したら、その中にdefault.nixというファイルを作成する。 最終的にはこうなっているはず。

    └── pkgs
        └── mypkg
            └── default.nix
    

    default.nixというファイルはNixにおいて特別な名前になっている。 ディレクトリにこの名前にファイルがある場合、ほかのNixファイルからimport mypkgという形式でimportできる。

    Node.jsやっている人はindex.jsみたいなものだと考えても良いと思う。

    パッケージを定義する

    パッケージは単一のDerivationを出力する関数として定義される。 nur-packages-templateのサンプルを見てみるとこんな風に定義されている。

    { stdenv }:
    
    stdenv.mkDerivation rec {
      name = "example-package-${version}";
      version = "1.0";
      src = ./.;
      buildPhase = "echo echo Hello World > example";
      installPhase = "install -Dm755 example $out";
    }

    pkgs/example-package/default.nix

    単純にecho Hello Worldが書き込まれたexampleという実行可能ファイルを作成する。 それを$outへとコピーする。

    別にcpでの良いのだけど、権限の設定などを一度に行えるためinstallが使われるケースが多い。 このDerivationではstdenvを使っているので、引数にstdenvを指定している。

    これはflakeで言うoutput関数の引数に近いもの。ここにnixpkgsのパッケージ名を指定するとそのパッケージ定義を参照できる。

    外部から参照できるようにする

    このままだと外部から参照できないので、外部から参照できるようパッケージ定義を追加する。

    ディレクトリルートにあるdefault.nixを開くとこんな感じのコードが書かれているはず。

    # This file describes your repository contents.
    # It should return a set of nix derivations
    # and optionally the special attributes `lib`, `modules` and `overlays`.
    # It should NOT import <nixpkgs>. Instead, you should take pkgs as an argument.
    # Having pkgs default to <nixpkgs> is fine though, and it lets you use short
    # commands such as:
    #     nix-build -A mypackage
    
    { pkgs ? import <nixpkgs> { } }:
    
    {
      # The `lib`, `modules`, and `overlays` names are special
      lib = import ./lib { inherit pkgs; }; # functions
      modules = import ./modules; # NixOS modules
      overlays = import ./overlays; # nixpkgs overlays
    
      example-package = pkgs.callPackage ./pkgs/example-package { };
      # some-qt5-package = pkgs.libsForQt5.callPackage ./pkgs/some-qt5-package { };
      # ...
    }

    default.nix

    example-packageが該当の箇所になる。この記述をすることでパッケージ定義が公開され、外部から参照できる。

    Cachixを使う

    これで外部から参照できるパッケージを定義/公開できた。 ただ、このままだとこのパッケージを参照したユーザーのマシンでもbuildが走ってしまう。

    Nixは羃等性を担保する設計になっているので、同じ定義ならどのマシンでビルドしても同等のバイナリが出力されることが保証される

    この性質を利用したのがバイナリキャッシュと呼ばれるもので、Cachixと使うと簡単に個人単位のバイナリキャッシュを構築できる。

    .github/workflows/build.ymlを開く。 すでにworkflowの定義が記述されているので、後は指定された箇所を書き換えるだけで使えるようになる。

    書き換える箇所は以下の通り。

    • nurRepo
      このテンプレート自体NUR(Nix User Repository)というリポジトリに登録するためのものになっている。 これに登録したい場合はここに自身の名前を指定する必要がある。なおNURへの登録にはPRが必要なため、それをせず個人的なリポジトリとしても使用できる。

    • cachixName
      Cachixでキャッシュ作成時に指定した名前を指定する。 たとえば、mycache.cachix.orgというキャッシュを使いたい場合はmycacheを指定する。

    GitHub Secret

    Cachixの認証で使用するSecretを設定する。

    • CACHIX_SIGNING_KEY
      Nixのバイナリは鍵で署名されているのだけど、それを指定するためのもの。 今回は使わないけれど、気になる人はGetting Startedから詳しい説明が載っている。

    • CACHIX_AUTH_TOKEN
      Cachixのサイトから生成できるトークン。今回はこれを使う。

    以下の手順でCACHIX_AUTH_TOKENを取得できる。

    Cachixのキャッシュ一覧からダッシュボードを開き、Settingsを開く。 Auth Tokensというというページがあるので、Generateボタンから生成できる。 するとトークンが表示されるのでコピーしておく。

    トークンが取得できたらGitHubリポジトリの設定をしていく。 nur-packagesリポジトリのSettingsを開き、Secrets and variablesをクリックする。 一覧が展開されるので、一番上にあるActionsをクリックする。 ページ下部にRepository secretsという項目があるので、その右にあるNew repository secretボタンをクリックする。 登録画面に遷移するので、

    • NameにCACHIX_AUTH_TOKEN
    • Secretに先程コピーしたトークン

    を入力し、Add secretをクリックする。

    登録が完了したらリポジトリをpushする度にActionsが走ってCachixにキャッシュがアップロードされるのが確認できるはず。

    実際に使ってみる

    これでオレオレNURが構築できたので、実際に使ってみる。

    まずflakeを用意する。

    以下のコマンドを実行すると良い感じのFlakeが作成できるので、特にこだわりがない人はこれを使うのがお勧め。

    nix flake init -t github:Comamoca/scaffold#flake-basic

    flake.nixを開いたらinputsの要素にnur-packages = "github:自分のユーザー名/リポジトリ名"を追加する。 また、outputの引数にnur-packagesを追加する。

    今のところこうなっているはず。

    inputs = {
      nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
      treefmt-nix.url = "github:numtide/treefmt-nix";
      flake-parts.url = "github:hercules-ci/flake-parts";
      systems.url = "github:nix-systems/default";
      nur-packages.url = "github:自分のユーザー名/リポジトリ名" # <- これを追加した
    };
    outputs =
      inputs@{
        self,
        systems,
        nixpkgs,
        treefmt-nix,
        flake-parts,
        nur-packages # <- これを追加した
      }:

    次に、nur = nur-packages.legacyPackages.${system};stdenv = pkgs.stdenv;の下に追加する。

    こうなっているはず。

    perSystem =
      {
        config,
        pkgs,
        system,
        ...
      }:
      let
        stdenv = pkgs.stdenv;
        nurpkgs = nur-packages.legacyPackages.${system}; # <- これを追加した
    
        # 以下省略

    これでnurpkgs.パッケージ名でパッケージを参照できるようになった。

    home-managerでこれらのパッケージを使う際は上記のようにinputsを定義して、 flake.nixが読み込むNixファイルでnurpkgs = inputs.nur-packages.legacyPackages.${system};と定義することで同様に利用できる。

    定義したnurpkgsは通常のnixpkgsと同じ感覚で利用できる。 たとえば自分はこんな感じで使っている。

    まとめ

    • オレオレFlakeが増えてきたらオレオレNURを構築すると便利
    • Cachixを有効にすることでほかのユーザーがパッケージを利用した際にビルドを高速化できる
    • 独自のNixリポジトリを構築することで、nixpkgsへのコントリビュートの足掛りとしても期待できる

    オレオレNURで生活がますます便利になったと感じているので、これらかもNixしていきたい。