Nixでdotfiles管理できるようになるまでのメモ
Nixに入門しようと思うので、リアルタイムに状況をメモしていきます。
目的
Nixでdotfiles管理ができるようになる
要件
- 現状のchezmoiでのdotfilesでできてたこと全部できる
 - マルチプラットフォーム対応(
x86_64-linuxとaarch64-linuxは必須。できればaarch64-darwinも) - 美しいディレクトリ構造
- パッケージごとにファイルを分けたい
 
 
最初の一歩
以下の記事を読んだ。
これにより、Nixに対する一つの誤解が解けた。
before
- Nixはnpmのようなパッケージマネージャ。プロジェクトの依存関係を宣言的に管理できる
 - NixOSを使用することで、ユーザ環境やOSの設定も宣言的に管理できるようになる
 
after
- NixOSではなくNix単体でも、profileという概念によってユーザ環境を宣言的に管理できる
 
Home Managerってなんだ?
home-managerは上記のNixパッケージマネージャを利用して、ローカルユーザー環境で利用するパッケージの管理とdotfilesを一元的に管理するための仕組みです。
home-managerを使わなくてもprofileによってパッケージをグローバルインストールすることは可能だが、home-managerを使うことによって.bashrcなどの設定ファイルなどもNixの管理下にできるという認識。
環境構築に入る前に
自分のやりたいことがNixで実現できそうなことが分かったので、Nixの環境構築に入っていく。
ただし、私はchezmoiも気に入っているのでchezmoiと組み合わせることを検討する。
chezmoiにはNixの環境構築とusernameやemailなどの設定をお願いしたい。
環境構築
Git & xz-utils
chezmoiはgit、Nixはgitとxz-utilsが必要みたいなのでこれだけはaptでインストールしておく。
なお、環境はm2のmacOSでOrbStackを用いて立ち上げたaarch64-linuxのUbuntuである。
sudo apt install -y git xz-utils
chezmoi
個人的に~/binにインストールされるのが気に入らないので、~/.local/binにインストールする。
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b $HOME/.local/bin
chezmoi init
Nix
DeterminateSystemsが提供している方でインストールする。
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
chezmoiのディレクトリに移動する。
chezmoi cd
Nixはgit管理下にある場合、追跡中のファイルしか認識されないようになるので一旦gitを削除しておく。
rm -rf .git
チュートリアル
一旦home-managerは置いておいて、以下の記事に沿って試してみる。
以降は基本的に~/.local/share/chezmoiディレクトリで操作を行っていく。
flake.nixの作成
neovimの設定ができるまではホストマシンのvscodeでコードを書いていく。
rootにflake.nixを作成し、以下のコードを書く。アーキテクチャはaarch64-linuxに変更している。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };
  outputs = { self, nixpkgs }: {
    packages.aarch64-linux.my-packages = nixpkgs.legacyPackages.aarch64-linux.buildEnv {
      name = "my-packages-list";
      paths = [
        nixpkgs.legacyPackages.aarch64-linux.git
        nixpkgs.legacyPackages.aarch64-linux.curl
      ];
    };
  };
}
ここまでできたらprofileをinstallする。
nix profile install .#my-packages
(.#がなんなのかはよく分かっていない)
完了したら
ls ~/.nix-profile/bin
を実行し、中身があったら成功。
フォーマッタの設定
どうやらNixではフォーマッタを設定できるみたいなので、フォーマッタを設定する。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };
  outputs = { self, nixpkgs }: {
    formatter.aarch64-linux = nixpkgs.legacyPackages.aarch64-linux.nixfmt-rfc-style;
    packages.aarch64-linux.my-packages = nixpkgs.legacyPackages.aarch64-linux.buildEnv {
      name = "my-packages-list";
      paths = [
        nixpkgs.legacyPackages.aarch64-linux.git
        nixpkgs.legacyPackages.aarch64-linux.curl
        nixpkgs.legacyPackages.aarch64-linux.nixfmt-rfc-style
      ];
    };
  };
}
nixfmt-rfc-styleというものをインストールするように変更。以下のコマンドで変更を反映させる。
nix flake update
nix profile upgrade my-packages
ls ~/.nix-profile/bin
してnixfmtがあったら成功。
nix fmt
でフォーマットできるようになる。
neovim
neovimのnightlyを入れるなら、nixpkgsじゃなくてneovim-nightly-overlayから入れるらしい。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
  };
  outputs =
    {
      self,
      nixpkgs,
      neovim-nightly-overlay,
    }:
    {
      formatter.aarch64-linux = nixpkgs.legacyPackages.aarch64-linux.nixfmt-rfc-style;
      packages.aarch64-linux.my-packages = nixpkgs.legacyPackages.aarch64-linux.buildEnv {
        name = "my-packages-list";
        paths = [
          nixpkgs.legacyPackages.aarch64-linux.git
          nixpkgs.legacyPackages.aarch64-linux.curl
          nixpkgs.legacyPackages.aarch64-linux.nixfmt-rfc-style
          neovim-nightly-overlay.packages.aarch64-linux.neovim
        ];
      };
    };
}
nix flake update
nix profile upgrade my-packages
nvim
neovimが起動できたら成功。
変数の設定
aarch64-linuxとnixpkgs.legacyPackagesという文字列を何回も書いているのが冗長なので、変数でまとめる。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
  };
  outputs =
    {
      self,
      nixpkgs,
      neovim-nightly-overlay,
    }:
    let
      system = "aarch64-linux";
      pkgs = nixpkgs.legacyPackages.${system}.extend (neovim-nightly-overlay.overlays.default);
    in
    {
      formatter.${system} = pkgs.nixfmt-rfc-style;
      packages.${system}.my-packages = pkgs.buildEnv {
        name = "my-packages-list";
        paths = with pkgs; [
          git
          curl
          nixfmt-rfc-style
          neovim
        ];
      };
    };
}
更新タスクの追加
nix flake update
nix profile upgrade my-packages
これを毎回書くのは面倒なので、一コマンドで完結するようにする。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
  };
  outputs =
    {
      self,
      nixpkgs,
      neovim-nightly-overlay,
    }:
    let
      system = "aarch64-linux";
      pkgs = nixpkgs.legacyPackages.${system}.extend (neovim-nightly-overlay.overlays.default);
    in
    {
      formatter.${system} = pkgs.nixfmt-rfc-style;
      packages.${system}.my-packages = pkgs.buildEnv {
        name = "my-packages-list";
        paths = with pkgs; [
          git
          curl
          nixfmt-rfc-style
          neovim
        ];
      };
      apps.${system}.update = {
        type = "app";
        program = toString (
          pkgs.writeShellScript "update-script" ''
            set -e
            echo "Updating flake..."
            nix flake update
            echo "Updating profile..."
            nix profile upgrade my-packages
            echo "Update complete!"
          ''
        );
      };
    };
}
以下のコマンドで実行。
nix run .#update
ここで基礎へ
Nixがどういうものかふんわりと分かってきたので、ここで基礎をちゃんと読む。
そういえば、マルチプラットフォーム……
そういえば、マルチプラットフォーム対応したいのであった。現在はsystem = "aarch64-linux";と指定してしまっているので、これを無くしたい。
そこで、上記の記事で紹介されているflake-utilsを使うことにした。flake-utilsのflake-utils.lib.eachDefaultSystemを使用することで、マルチプラットフォーム対応することができる。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system}.extend (neovim-nightly-overlay.overlays.default);
      in
      {
        formatter.${system} = pkgs.nixfmt-rfc-style;
        packages.${system}.my-packages = pkgs.buildEnv {
          name = "my-packages-list";
          paths = with pkgs; [
            git
            curl
            nixfmt-rfc-style
            neovim
          ];
        };
        apps.${system}.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating profile..."
              nix profile upgrade my-packages
              echo "Update complete!"
            ''
          );
        };
      }
    );
}
これで反映させてみると、なんとエラー発生。
error: flake 'path:/home/.../.local/share/chezmoi' does not provide attribute 'apps.aarch64-linux.update', 'packages.aarch64-linux.update', 'legacyPackages.aarch64-linux.update' or 'update'
どうやらflake-utils.lib.eachDefaultSystem内では、formatter.${system}やpackages.${system}についていた${system}は要らないみたい。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system}.extend (neovim-nightly-overlay.overlays.default);
      in
      {
        formatter = pkgs.nixfmt-rfc-style;
        packages.my-packages = pkgs.buildEnv {
          name = "my-packages-list";
          paths = with pkgs; [
            git
            curl
            nixfmt-rfc-style
            neovim
          ];
        };
        apps.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating profile..."
              nix profile upgrade my-packages
              echo "Update complete!"
            ''
          );
        };
      }
    );
}
これで再度実行。
nix run .#update
    Updating flake...
    Updating profile...
    Update complete!
無事成功。
お片付け
Nixの環境を何度も作り直していると古い環境がたまっていくので、以下のコマンドで片付ける。
nix store gc
さて、home-manager
ここからはhome-managerに入っていきたい。流れとしては、以下のようになると思われる。
- 最小構成でhome-manager導入
 - 現在のdotfilesでの設定の移植
 - パッケージごとにディレクトリを分ける
 
ちなみに、3が実現できるのかは確認しきれていない。ただ、GitHubで検索したところそれっぽいことをしてるリポジトリはいくつか見つけた。
この記事と合わせてhome-managerを導入していく。
とりあえず導入
以下の設定でビルドしてみる。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      home-manager,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }@inputs:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        inherit (import ./home/options.nix) username;
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter = pkgs.nixfmt-rfc-style;
        apps.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating home-manager..."
              nix run nixpkgs#home-manager -- switch --flake .#${username}
              echo "Update complete!"
            ''
          );
        };
        homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration {
          pkgs = pkgs;
          extraSpecialArgs = {
            inherit inputs;
          };
          modules = [
            ./home/home.nix
          ];
        };
      }
    );
}
rec {
  username = "airrnot";
  gitUsername = "airrnot";
  gitEmail = "airrnot@example.com";
}
{
  inputs,
  lib,
  config,
  pkgs,
  ...
}:
let
  inherit (import ./options.nix) username;
in
{
  nixpkgs = {
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
  };
  programs.home-manager.enable = true;
}
以下のコマンドでビルド実行。
nix run nixpkgs#home-manager -- switch --flake .#airrnot
と、ここで、エラー発生。
error: flake 'path:/home/airrnot/.local/share/chezmoi' does not provide attribute 'packages.aarch64-linux.homeConfigurations."airrnot".activationPackage', 'legacyPackages.aarch64-linux.homeConfigurations."airrnot".activationPackage' or 'homeConfigurations."airrnot".activationPackage'
んー、よく分からない。とりあえずここで詰み。
うわ、flake-utils.lib.eachDefaultSystemを外して
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      home-manager,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }@inputs:
      let
        inherit (import ./home/options.nix) username;
        system = "aarch64-linux";
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter.${system} = pkgs.nixfmt-rfc-style;
        apps.${system}.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating home-manager..."
              nix run nixpkgs#home-manager -- switch --flake .#${username}
              echo "Update complete!"
            ''
          );
        };
        homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration {
          pkgs = pkgs;
          extraSpecialArgs = {
            inherit inputs;
          };
          modules = [
            ./home/home.nix
          ];
        };
      };
}
だと成功した。
どういうことですか。
解決
GitHubで実装例を調べまくってなんとか解決した。
flake-utils.lib.eachDefaultSystemを使用している場合は、homeConfigurationsをlegacyPackagesで囲まなければならなかった。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      home-manager,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }@inputs:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        inherit (import ./home/options.nix) username;
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter = pkgs.nixfmt-rfc-style;
        apps.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating home-manager..."
              nix run nixpkgs#home-manager -- switch --flake .#${username}
              echo "Update complete!"
            ''
          );
        };
        legacyPackages = {
          inherit (pkgs) home-manager;
          homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration {
            pkgs = pkgs;
            extraSpecialArgs = {
              inherit inputs;
            };
            modules = [
              ./home/home.nix
            ];
          };
        };
      }
    );
}
なんでこれで解決できるのかはよく分からない。
が、とりあえず良かった。
自分でビルドするには?
nixpkgsやneovimのような他のコミュニティでも公開されていないようなパッケージはどうやってビルドするんだ? ということで、Chat GPTに聞いてみた。
ここではオシャレな時計を表示するcliツールであるeraを例に行う。eraはdenoで書かれている。
ChatGPT's answer
{
  description = "Build the 'era' project using Deno";
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs"; # nixpkgs のソース
  };
  outputs = { self, nixpkgs }: {
    packages = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: let
      pkgs = import nixpkgs { inherit system; };
    in
      {
        era = pkgs.stdenv.mkDerivation rec {
          pname = "era";
          version = "1.0.0"; # 必要に応じてバージョンを変更
          src = pkgs.fetchFromGitHub {
            owner = "kyoheiu";
            repo = "era";
            rev = "main"; # 特定のバージョンやコミットハッシュを指定するならここを変更
            sha256 = lib.fakeSha256; # 実際のsha256は `nix-prefetch-git` を使用して取得
          };
          buildInputs = [ pkgs.deno ];
          buildPhase = ''
            deno task build
          '';
          installPhase = ''
            mkdir -p $out/bin
            cp -r ./dist/* $out/bin/
          '';
          meta = with pkgs.lib; {
            description = "ERA - A tool built on Deno";
            license = licenses.mit;
            platforms = platforms.all;
          };
        };
      }
    );
  };
}
どうやら、pkgs.stdenv.mkDerivationが肝らしい。
そもそも、flakeってどうやって書くんだっけ?
outputsは関数になっており、Flakeの評価時にinputsに指定したFlakeのoutputsが引数に渡されます。outputsはAttribute Setを返します。
実は、outputsで返せるのはパッケージだけではありません。outputsには以下のようなものを指定できます。
{
  inputs = {
    # 依存するFlake
  };
  outputs = inputs: {
    packages."<システムアーキテクチャ>"."<パッケージ名>" = derivation;
    devShells."<システムアーキテクチャ>"."<devShellの名前>" = derivation;
    formatter."<システムアーキテクチャ>"."<パッケージの名前>" = derivation;
    templates."<テンプレートの名前>" = {
      path = "<ストアパス>";
      description = "テンプレートの説明";
    };
    # ...その他多数
  };
}
packagesにはderivationを渡すと書かれている。では、derivationとは何か?
ここでpkgs.stdenv.mkDerivationに戻ってきた。
何となくビルドの流れが理解できた。
では次に、ソースの取得までNixで完結させる方法について調べる。
なんかだめっぽい
nix run .#update
Updating flake...
warning: updating lock file '/home/airrnot/.local/share/chezmoi/flake.lock':
• Updated input 'era':
    'path:./packages/era?lastModified=1&narHash=sha256-AD8Gu7WYoGKr0JjQ9idWFQObxPGr0siccQ8/a0q2H4s%3D' (1970-01-01)
  → 'path:./packages/era?lastModified=1&narHash=sha256-3yFKi6f4QNy/WX%2Br3AkSN9ZxjFv56qb0u/%2BIjYieyq4%3D' (1970-01-01)
Updating home-manager...
error: builder for '/nix/store/9a932srxsnfc7x3a5rzda7qwqsqm7jgx-era-0.1.3.drv' failed with exit code 2;
       last 25 log lines:
       >
       > Caused by:
       >     Error code 14: Unable to open the database file)
       > Failed to open cache file '/homeless-shelter/.cache/deno/dep_analysis_cache_v2', opening in-memory cache.
       > Could not initialize cache database '/homeless-shelter/.cache/deno/check_cache_v2', deleting and retrying... (unable to open database file: /homeless-shelter/.cache/deno/check_cache_v2
       >
       > Caused by:
       >     Error code 14: Unable to open the database file)
       > Failed to open cache file '/homeless-shelter/.cache/deno/check_cache_v2', performance may be degraded.
       > Check file:///build/source/src/main.ts
       > Could not initialize cache database '/homeless-shelter/.cache/deno/fast_check_cache_v2', deleting and retrying... (unable to open database file: /homeless-shelter/.cache/deno/fast_check_cache_v2
       >
       > Caused by:
       >     Error code 14: Unable to open the database file)
       > Failed to open cache file '/homeless-shelter/.cache/deno/fast_check_cache_v2', performance may be degraded.
       > Compile file:///build/source/src/main.ts to era
       > Download https://dl.deno.land/release/v1.46.2/denort-aarch64-unknown-linux-gnu.zip
       > error: Writing temporary file 'era.tmp-cea1351b875beb7b'
       >
       > Caused by:
       >     0: error sending request for url (https://dl.deno.land/release/v1.46.2/denort-aarch64-unknown-linux-gnu.zip): client error (Connect): dns error: failed to lookup address information: Temporary failure in name resolution: failed to lookup address information: Temporary failure in name resolution
       >     1: client error (Connect)
       >     2: dns error: failed to lookup address information: Temporary failure in name resolution
       >     3: failed to lookup address information: Temporary failure in name resolution
       > make: *** [Makefile:7: install] Error 1
       For full logs, run 'nix log /nix/store/9a932srxsnfc7x3a5rzda7qwqsqm7jgx-era-0.1.3.drv'.
error: 1 dependencies of derivation '/nix/store/vi4b6w729zk5nd5xc3dpfj54hscpfaz7-home-manager-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/v1lwv35yvd6wlw0z8l9qrwpx1ba6al0f-home-manager-generation.drv' failed to build
よく考えればdenoもネットを介して依存関係を解決するわけで、普通の方法じゃ無理に決まっている。
次はこれを試したい
いけた
Denoでコンパイルするのは一旦諦めて、とりあえずコンパイル済みのバイナリをfetchしてきてインストールすることにした。
以下のようなディレクトリ構造だとして、
.
├── flake.lock
├── flake.nix
├── home
│   ├── home.nix
│   └── options.nix
└── packages
    └── era.nix
それぞれのソースは以下の通り。
{
  pkgs ? import <nixpkgs> { },
}:
pkgs.stdenv.mkDerivation {
  name = "era";
  src = builtins.fetchTarball {
    url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-aarch64-linux.tar.gz";
    sha256 = "0r804l7l5xlxzyap32i5q2l706xfgwsdqiylw71rk6hk2m487w79";
  };
  phases = [ "installPhase" ];
  installPhase = ''
    mkdir -p $out/bin
    cp $src/era $out/bin/era
    chmod +x $out/bin/era
  '';
}
{
  inputs,
  lib,
  config,
  pkgs,
  ...
}:
let
  inherit (import ./options.nix) username;
  era = import ../packages/era.nix { inherit pkgs; };
in
{
  nixpkgs = {
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
    packages = with pkgs; [
      nixfmt-rfc-style
      era
    ];
  };
  programs.home-manager.enable = true;
}
home.nixでera.nixをimportして、home.packagesに加えればインストールすることができた。
しかし、現状ではaarch64-linuxにしか対応していないので、マルチプラットフォーム対応を考える。
モジュール化した場合のマルチプラットフォーム対応
目標は、flake.nixでflake-utils.lib.eachDefaultSystemで取得しているsystemをera.nixに渡すことである。そのためには、flake.nix -> home.nix -> era.nixという橋渡しが必要である。
home.nixにsystemを渡す
extraSpecialArgsに指定した値は、home.nixで引数として受け取ることが出来る。
{
  description = "Minimal package definition for aarch64-linux";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    {
      self,
      nixpkgs,
      home-manager,
      neovim-nightly-overlay,
      flake-utils,
      ...
    }@inputs:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        inherit (import ./home/options.nix) username;
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter = pkgs.nixfmt-rfc-style;
        apps.update = {
          type = "app";
          program = toString (
            pkgs.writeShellScript "update-script" ''
              set -e
              echo "Updating flake..."
              nix flake update
              echo "Updating home-manager..."
              nix run nixpkgs#home-manager -- switch --flake .#${username} --show-trace
              echo "Update complete!"
            ''
          );
        };
        legacyPackages = {
          inherit (pkgs) home-manager;
          homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration {
            pkgs = pkgs;
            extraSpecialArgs = {
              inherit inputs system;
            };
            modules = [
              ./home/home.nix
            ];
          };
        };
      }
    );
}
era.nixにsystemを渡す
import関数は第ニ引数に指定した値を引数として渡すことが出来る。そのため、以下のようにしてsystemをera.nixに渡す。
{
  inputs,
  lib,
  config,
  pkgs,
  system,
  ...
}:
let
  inherit (import ./options.nix) username;
  era = import ../packages/era.nix { inherit pkgs system; };
in
{
  nixpkgs = {
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
    packages = with pkgs; [
      nixfmt-rfc-style
      era
    ];
  };
  programs.home-manager.enable = true;
}
era.nixでsrcをsystemによって分岐させる
if式で頑張って分岐させる。
{
  pkgs ? import <nixpkgs> { },
  system,
}:
pkgs.stdenv.mkDerivation {
  name = "era";
  src =
    if system == "x86_64-linux" then
      builtins.fetchTarball {
        url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-x86_64-linux.tar.gz";
        sha256 = "0inf53m863vavvh6fg4dqys0sffrignsq426ybv3sfdfyi9g75jd";
      }
    else if system == "aarch64-linux" then
      builtins.fetchTarball {
        url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-aarch64-linux.tar.gz";
        sha256 = "0r804l7l5xlxzyap32i5q2l706xfgwsdqiylw71rk6hk2m487w79";
      }
    else if system == "x86_64-darwin" then
      builtins.fetchTarball {
        url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-x86_64-darwin.tar.gz";
        sha256 = "1g5jxlzpl955730p8b5gqrzvdnk92nfyn3fqwhyissh00iysqdiq";
      }
    else if system == "aarch64-darwin" then
      builtins.fetchTarball {
        url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-aarch64-darwin.tar.gz";
        sha256 = "165p4qp20q6wy19fxa40w2108xa45ri3g1h9jwrhqrd29vmbxvh4";
      }
    else
      throw "not supported system";
  phases = [ "installPhase" ];
  installPhase = ''
    mkdir -p $out/bin
    cp $src/era $out/bin/era
    chmod +x $out/bin/era
  '';
}
追記: と思いきや
systemはpkgs.systemで大丈夫だった。
importsってのがあるらしい
home.packagesとprograms.*は同じファイルで設定できないのかと思ってたが、普通にできるらしい。以下の記事を参照。
なので、eraの設定をhome/packages/に移動した。ついでにbashの設定も入れて現在のディレクトリ構造はこんな感じ。
.
├── flake.lock
├── flake.nix
└── home
    ├── home.nix
    ├── modules
    │   ├── bash.nix
    │   └── era.nix
    └── options.nix
{ pkgs, ... }:
{
  programs.bash = {
    enable = true;
  };
}
{
  pkgs,
  system,
}:
{
  home.packages = with pkgs; [
    (pkgs.stdenv.mkDerivation {
      name = "era";
      src =
        if system == "x86_64-linux" then
          builtins.fetchTarball {
            url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-x86_64-linux.tar.gz";
            sha256 = "0inf53m863vavvh6fg4dqys0sffrignsq426ybv3sfdfyi9g75jd";
          }
        else if system == "aarch64-linux" then
          builtins.fetchTarball {
            url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-aarch64-linux.tar.gz";
            sha256 = "0r804l7l5xlxzyap32i5q2l706xfgwsdqiylw71rk6hk2m487w79";
          }
        else if system == "x86_64-darwin" then
          builtins.fetchTarball {
            url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-x86_64-darwin.tar.gz";
            sha256 = "1g5jxlzpl955730p8b5gqrzvdnk92nfyn3fqwhyissh00iysqdiq";
          }
        else if system == "aarch64-darwin" then
          builtins.fetchTarball {
            url = "https://github.com/airRnot1106/era/releases/download/v0.1.3/era-v0.1.3-aarch64-darwin.tar.gz";
            sha256 = "165p4qp20q6wy19fxa40w2108xa45ri3g1h9jwrhqrd29vmbxvh4";
          }
        else
          throw "not supported system";
      phases = [ "installPhase" ];
      installPhase = ''
        mkdir -p $out/bin
        cp $src/era $out/bin/era
        chmod +x $out/bin/era
      '';
    })
  ];
}
{
  inputs,
  lib,
  config,
  pkgs,
  system,
  ...
}:
let
  inherit (import ./options.nix) username;
in
{
  nixpkgs = {
    overlays = [
      inputs.neovim-nightly-overlay.overlays.default
    ];
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
  };
  imports = [
    ./modules/bash.nix
    (import ./modules/era.nix {
      inherit pkgs system;
    })
  ];
  programs.home-manager.enable = true;
}
importsの中で追加で引数を渡したいなら、上記のように書けばいける。
と、思いきや普通に
{
  inputs,
  lib,
  config,
  pkgs,
  system,
  ...
}:
let
  inherit (import ./options.nix) username;
in
{
  nixpkgs = {
    overlays = [
      inputs.neovim-nightly-overlay.overlays.default
    ];
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
  };
  imports = [
    ./modules/bash.nix
    ./modules/era.nix
  ];
  programs.home-manager.enable = true;
}
でも行けた。勝手にsystemも渡してくれているらしい。era.nixで引数に...を追加するのを忘れずに。
というか、programs.*.enable = tureにすれば、home.packagesに書かなくてもインストールしてくれるのか。
現在のディレクトリ構造
.
├── flake.lock
├── flake.nix
└── home
    ├── home.nix
    ├── modules
    │   ├── bash.nix
    │   ├── era.nix
    │   ├── eza.nix
    │   ├── fd.nix
    │   ├── genact.nix
    │   ├── git.nix
    │   ├── neovim.nix
    │   ├── nix.nix
    │   ├── oh-my-posh.nix
    │   └── tree.nix
    └── options.nix
次のステップ
現状はmodules直下にすべてを詰め込んでいるので、カテゴリ分けをしたい。カテゴリの粒度は慎重に決めていきたい。
カテゴリー案
- shell
- bashとかzshとかoh-my-poshとか
 
 - dev
- プログラミング言語系。languageにするかは迷いどころ
 
 - misc
- genactとかのおまけツール
 
 
PATHを追加する
ところで、bashをenableにすると、PATHから~/.local/binが消滅してしまい、chezmoiが使えなくなる。よって、自分でPATHを通す必要がある。
home.sessionPathにPATHを書くと、PATHを通してくれる。
{
  inputs,
  lib,
  config,
  pkgs,
  system,
  mkPnpmPackages,
  ...
}:
let
  inherit (import ./options.nix) username;
in
{
  nixpkgs = {
    overlays = [
      inputs.neovim-nightly-overlay.overlays.default
    ];
    config = {
      allowUnfree = true;
    };
  };
  home = {
    username = username;
    homeDirectory = "/home/${username}";
    sessionPath = [ "$HOME/.local/bin" ];
    # https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
    stateVersion = "24.05";
  };
  imports = [
    ./modules/bash.nix
    ./modules/era.nix
    ./modules/eza.nix
    ./modules/fd.nix
    ./modules/genact.nix
    ./modules/git.nix
    ./modules/neovim.nix
    ./modules/nix.nix
    ./modules/oh-my-posh.nix
    ./modules/tree.nix
  ];
  programs.home-manager.enable = true;
}
NPM Packages
npmでグローバルインストールするパッケージたちもNixの管理下に置きたい。
ここに含まれているものなら、
home.packages = with pkgs; [
    nodePackages.${パッケージ名}
];
でインストールできるのだが、含まれていないものは自分でどうにかする必要がある。今回はaicommitsをインストールしたい。
こちらの設定を参考に、以下のように設定を書いた。
{
  pkgs,
  ...
}:
let
  nodejs = pkgs.nodejs;
  pnpm = pkgs.pnpm;
in
{
  home.packages = with pkgs; [
    (pkgs.stdenv.mkDerivation rec {
      pname = "aicommits";
      version = "1.11.0";
      src = fetchFromGitHub {
        owner = "Nutlope";
        repo = "aicommits";
        rev = "604def8284361b8827087350fe6fcb6d9e2de836";
        hash = "sha256-JWZywM/pJNG2HbIuM8jqOVEMomvFmLnZjmkJfy9M1j8=";
      };
      nativeBuildInputs = [
        nodejs
        pnpm.configHook
        makeWrapper
      ];
      pnpmDeps = pnpm.fetchDeps {
        inherit pname version src;
        hash = "sha256-uRCQOdF2Lki3e71hMq4vDFp1921+0Ety/T+WsUmoxGA=";
      };
      buildPhase = ''
        runHook preBuild
        pnpm build
        runHook postBuild
      '';
      installPhase = ''
        runHook preInstall
        mkdir -p $out/{lib,bin}
        cp -r {node_modules,dist} $out/lib
        makeWrapper $out/lib/dist/cli.mjs $out/bin/aicommits
        runHook postInstall
      '';
    })
  ];
}
流れとしては、fetchFromGitHubでソースを持ってきて、pnpmでbuildするというもの。
正直中身はよくわかってない。具体的にはpnpm.fetchDepsで指定するhashとmakeWrapperとは何かという部分。
色々整理
意地のモジュール化を行い、現在のディレクトリ構造は以下の通り。
.
├── flake.lock
├── flake.nix
└── home
    ├── home.nix
    ├── modules
    │   ├── default.nix
    │   ├── dev
    │   │   ├── default.nix
    │   │   ├── languages
    │   │   │   ├── default.nix
    │   │   │   ├── deno.nix
    │   │   │   ├── erlang.nix
    │   │   │   ├── gleam.nix
    │   │   │   ├── go.nix
    │   │   │   ├── node.nix
    │   │   │   └── python.nix
    │   │   ├── lsps
    │   │   │   ├── bash-language-server.nix
    │   │   │   └── default.nix
    │   │   └── tools
    │   │       ├── cmake.nix
    │   │       ├── default.nix
    │   │       ├── docker.nix
    │   │       ├── gcc.nix
    │   │       └── pnpm.nix
    │   ├── editor
    │   │   ├── default.nix
    │   │   └── neovim.nix
    │   ├── git
    │   │   ├── aicommits.nix
    │   │   ├── default.nix
    │   │   ├── git.nix
    │   │   └── lazygit.nix
    │   ├── nix
    │   │   ├── default.nix
    │   │   └── nixfmt-rfc-style.nix
    │   ├── shell
    │   │   ├── bash.nix
    │   │   ├── default.nix
    │   │   └── oh-my-posh.nix
    │   └── util
    │       ├── curl.nix
    │       ├── default.nix
    │       ├── era.nix
    │       ├── eza.nix
    │       ├── fd.nix
    │       ├── genact.nix
    │       ├── tree.nix
    │       ├── unzip.nix
    │       └── wget.nix
    └── options.nix
aicommits用のopenaiKeyを直接書きたくないので、機密情報を含むやつはchezmoiに任せる方針
本当はusernameとかも書きたくないけど、流石に不便なのでそこは妥協。
一段落
とりあえず形にはなったのでdotfilesを更新。
その後もなんやかんやあってひとまず完成!
2025/07/02時点のdotfilesのリポジトリはこちらです。