Nixファイルの整理方法: imports/import関数
始めに
前回書いたNixOSの記事の評判が良く、
「少しでもNixへチャレンジしてくれる人が増えてくれたら嬉しいな」と思う今日この頃です。
さて、今回は個人利用の範囲では常用運用できているNixOSですが、
どのように設定ファイルを分割して保守しているのか備忘録的にまとめたいと思います。
前提
早速ですが、先日公開された「Nix入門:ハンズオン」は是非一読していただきたいです。
上記の本で紹介されているNix言語の文法を元に、私が使用している内容に触れていきます。
この記事ではNixOSやhome-managerといった物に触れるということはせず、ファイル分割や設定のまとめ方を共有していきます。
…で、書いている最中に文章量がえげつないことになってきたので、今回はimport関数
とimports
に絞っていきます。
Nix言語に触れていると最初に遭遇する紛らわしい物です。
この2つの違いと活用方法を見ていきましょう。
対象読者
- 言われたまま設定を書いているけど、そろそろ整理したいと考えている人
- ファイルは分割しないと落ち着かない人
- Nix言語に慣れていきたい人
下記は長すぎるため、更に折り畳みに折り畳んでいます。
この状態が我慢できない人
これは私の/etc/nixos/configuration.nix
の残骸です。
現在は、flake化に伴い使用されていません。
configuration.nix
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
};
};
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "yasunori-desktop"; # Define your hostname.
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
# Set your time zone.
time.timeZone = "Asia/Tokyo";
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# console = {
# font = "Lat2-Terminus16";
# keyMap = "us";
# useXkbConfig = true; # use xkb.options in tty.
# };
# Enable the X11 windowing system.
# services.xserver.enable = true;
# Configure keymap in X11
# services.xserver.xkb.layout = "us";
# services.xserver.xkb.options = "eurosign:e,caps:escape";
# Enable CUPS to print documents.
# services.printing.enable = true;
# Enable sound.
# hardware.pulseaudio.enable = true;
# OR
# services.pipewire = {
# enable = true;
# pulse.enable = true;
# };
# Enable touchpad support (enabled default in most desktopManager).
# services.libinput.enable = true;
# Define a user account. Don't forget to set a password with ‘passwd’.
users.users.yasunori = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
packages = with pkgs; [];
};
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
git
curl
gnumake
];
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or
# https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "24.05"; # Did you read the comment?
}
私は1つのファイルに大量の記述がしてある状態が好きではないため、可能な限り分割したい派です。
雰囲気としては、Nixを始めた人の第2ステップといった感じでしょうか。
import関数
よく勘違いされますが、import
は関数です。
よいですか、import
はNix言語の組込み関数です。
組込み関数は基本的にbuiltins
のAttrSet
に含まれているのですが、使用頻度が高いためimport
だけで呼び出せるようになっています。
詳しくは上記のページの説明をご覧いただければ分かるかと思います。
import関数について簡単に説明しておくと、Nix言語が記述されているファイル読み込みAttrSet
として使えるようになります。
よくあるユースケースとしては次のように使用できます。
{
foo = {
hoge = "fuga";
};
}
{
hoge = (import ./hoge.nix).foo.hoge;
}
foo.nix
を読み込んで、そのままfoo.hoge
にアクセスしています。
このときconfiguration.nix
のhoge
には"fuga"
という文字列が格納されます。
default.nix
import関数
は基本Nix言語が記述されたファイルを読み込む物ですが、ディレクトリにdefault.nix
というファイルがあった場合は、
インポート時にそのディレクトリを指定すればdefault.nix
を読み込んでくれます。
その辺の挙動の説明については、上記のリンクを参照してもらうと分かりやすいです。
私の場合、home-managerの設定で次のような使い方をしています。
このとき、追う順番として次のようになります。
-
home-manager/default.nix
にhome-manager.lib.homeManagerConfiguration
という関数に渡す引数部分を定義 -
flake.nix
内でhome-manager/default.nix
をインポートして、定義しておいたAttrSetを引数に渡す
さらにdefault.nix
を使っていろいろしているのですが、蛇足になってしまいまうのでここまでの解説にします。
つまりdefault.nixは共通的な処理を書くときや、インポートしている箇所をシンプルにしたいとき便利ということが伝わればよいです!
imports
NixOSをカスタムして触っているconfiguration.nix
というのはモジュールであり、
NixOS/nixpkgsでメンテナンスされているモジュールによって定義された設定項目を触っているだけに過ぎません。
そしてimports
というものは、モジュールのAttrSet(属性)
の1つでしかありません。
それではimports
の役割は何かというと、別のモジュールへのパスを記述することで、そのモジュールによって定義された設定項目を使えるようにするという物になります。
ですが、imports
はそれだけでは終りません。
読み込みファイルのマージ機能
別のファイルに記述した設定をマージしてくれる機能があります。
前述したとおり、imports
はモジュールの読み込みがメインの機能ではありますが、モジュールによって宣言された設定項目を別のファイルにしても読み込んでくれるのです。
次の2つのファイルを見てみましょう。
このファイルのパスをimports
に記述するだけで、部分的に重複するAttrSet
であってもいい感じにマージしてくれます。
結果として内部では次のようになっています。
{
services = {
tlp = {
enable = true;
settings = {
# 中略
};
};
openssh = {
enable = true;
settings = {
# 中略
};
};
};
}
このとき注意すべき点は、トップレベルのAttributeから順番に記述しないといけません。
{
description = "SSH key agent";
wantedBy = [ "default.target" ];
serviceConfig = {
Type = "simple";
Environment = "SSH_AUTH_SOCK=%t/ssh-agent.socket";
ExecStart = "${pkgs.openssh}/bin/ssh-agent -D -a $SSH_AUTH_SOCK";
};
}
このようにパスやファイル名と内容からssh-agentのsystemd-unitであることは分かるのですが、このファイルをimports
に追加してもエラーで読み込んでくれません。
関数として宣言したファイルの読み込み
読み込もうとしたファイルが関数として記述されていても、次の引数が暗黙的に読み込み先の関数へ渡されます。
config
options
pkgs
modulePath
これらは公式のwikiで列挙されている物で、NixOSとhome-managerの場合で違います。
man
で_module.args
の内容を確認しておきましょう。
- NixOS =>
man 5 configuration.nix
- home-manager =>
man 5 home-configuration.nix
imports
で指定したファイルが関数として読み込むときに、_module.args
で宣言されているAttrSet
を引数に渡してくれます。
ただ、各ファイルに引数を全部列挙するのは手間ですし、LSPを使っていると使用していない引数が診断に引っ掛って邪魔です。
ちょうどフォントの設定がそれを回避しています。
フォントの設定に使用しているのはpkgs
だけですが、それ以外の引数は...
というものです。
詳しくは上記の内容をご覧頂ければ分かりますが、pkgs
だけを使用してそれ以外のAttrSet
は無視するということを...
はしています。
imports
に追加するファイルが関数の場合は、引数には...
のセットが必須といえるでしょう。
AttrSet
もマージしてくれる?!
ここまでの説明でimports
に記述できるのはファイルパスしか渡せないと思いますが、実はAttrSet
も含めて大丈夫なのです。
その証拠に上記のようにimport関数
を使ってファイルを読み込みAttrSet
になった物をimports
に入れても読み込んでいるのです!
…とは言っても、公式のWikiにそんな説明が無いので、vim-jpの#tech-nix
で呟いていたところ、
nixpkgsコミッターのnatsukium氏からの天の声を頂きました。
AttrSet
を読み込んでマージしてくれる謎を教えてくれました!
…おっと、Nix力が強すぎて何が書いてあるか分からないかもしれませんが、
ファイルパスを渡しても最終的にimport関数
を使っているし、AttrSet
でも読み込んで最終的にマージする処理をしてくれている…ようです。
補足(home-managerについて)
imports
の最初で説明したように、imports
自体はNixOSのモジュールとしての機能になります。
しかしhome-managerでも同じことができていますが、これはどういうことでしょうか。
実はhome-manager自体もNixOSのモジュールシステムを利用して作られているため、同じ文法で設定の定義が可能になっています。
詳しい理由に関しては、上記のコードを見ることで分かるそう…ですが、またNix力が強くて難しいと感じてしまうかもしれません。
とりあえず、NixOSのモジュールシステムを利用しているということが分かれば大丈夫かと思います。
まとめ
import関数
とimports
に関しては、私もNixOSを触り出して謎に思った部分でした。
現在はimport関数
とimports
を使いこなしているお陰で、設定ファイルを分割捗っています。
思った以上に難しい内容でしたが、これが皆さんの良きNixライフに繋がれば幸いです!
Discussion