設定ファイルの構成を考える
設定項目は徐々に増えていくのが確実ですので、最初からモジュールに分割して管理していきます。しかしモジュールを追加、分割するたびにインポートする側のコードが長くなる、数が増える、ディレクトリが深くなる、などしてきますので見通しが悪くなり管理大変です。
GitHubでStarのたくさんついた設定をまねて何度か書いてはリファクタリングして破棄してを繰り返していましたが、Dendritic Patternというパターンですすめていこうと思います。nixpkgsのコミッターでもあるPol Dellaiera氏のブログポストRefactoring My Infrastructure As Code Configurationsを一読されることをおすすめします1。
Dendritic Patternにリファクタリングする
ということで、Dendritic Pattern に沿って前回の設定をリファクタリングしていきます。
- flake化
- モジュールへの分割
- ディレクトリ構成を自由化する
import-treeの導入 -
flake-partsによるモジュールのクラス分け - ホスト別の設定への骨組み設計
の順にすすめます。
ただflake化してみる
~/infra/以下に作成していきます。
前回あつかった2つのモジュールが必要ですが、ロックできない絶対パスへのアクセスはimpureとなり許されないのでコピーします。
[~]$ mkdir infra
[~]$ cd infra
[~/infra]$ cp /etc/nixos/* ./
最初のflake.nixを作成します。
ついでにnixpkgはunstableを使うように変更しています。
{
description = "My NixOS configuration`";
inputs = {
nixpkgs.url = "github:/nixos/nixpkgs/nixpkgs-unstable";
};
outputs =
{ nixpkgs, ...}:
{
nixosConfigurations.x1 = nixpkgs.lib.nixosSystem {
modules = [ ./configuration.nix ];
};
};
}
[~/infra]$ sudo nixos-rebuild switch --flake .#x1
はい、できました。
configuration.nixを複数のモジュールに分割する
では今の設定をモジュールに分割していきます。
今のconfiguration.nixはコメントを外して整頓するとこれだけです。
ホスト名の設定とnetworkmanagerグループへの参加だけ追加しました。
{ config, lib, pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "x1"; # 追加
networking.networkmanager.enable = true;
time.timeZone = "Asia/Tokyo";
users.users.naogami = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" ]; # "networkmanager"追加
packages = with pkgs; [
];
};
services.openssh.enable = true;
system.stateVersion = "25.11";
}
これを機能別に分けてみます。
modules/以下にあとあとわかりやすよう適宜ディレクトリに分類して配置します。
ひとまず、
-
base/ホストによらず共有できそうなもの -
users/ユーザー毎の設定 -
openssh/openssh関連
としました。
hardware-configuration.nixモジュールもmodules/以下に移動しておきます。
{
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
};
};
}
{
boot.loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
}
{
networking = {
hostName = "x1";
networkmanager.enable = true;
};
}
{
time.timeZone = "Asia/Tokyo";
}
{ pkgs, ... }:
{
users.users.naogami = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" ];
packages = with pkgs; [
];
};
}
{
services.openssh.enable = true;
}
{
system.stateVersion = "25.11";
}
みてわかるとおりファイルを分けただけです。
これらを使用するようにflake.nixを書き換えます。
{
description = "My NixOS configuration`";
inputs = {
nixpkgs.url = "github:/nixos/nixpkgs/nixpkgs-unstable";
};
outputs =
{ nixpkgs, ...}:
{
nixosConfigurations.x1 = nixpkgs.lib.nixosSystem {
modules = [
./modules/hardware-configuration.nix
./modules/base/system/nix.nix
./modules/base/boot/loader.nix
./modules/base/networking/networking.nix
./modules/base/i18n/timezone.nix
./modules/users/naogami.nix
./modules/openssh/openssh.nix
./modules/base/system/state-version.nix
];
};
};
}
ビルドが通ることを確認したらいらなくなったconfiguration.nixを削除します。
[~/infra]$ sudo nixos-rebuild switch --flake .#x1
[~/infra]$ rm configuration.nix
import-treeでモジュールを一括インポートする
すぐに気づくことですが、すでにモジュールパスの羅列がしんどいです。
各ディレクトリにdefault.nixを配置してそちらにインポートするコードを書けば多少ましではありますが、モジュールを足すたびに複数のファイルを修正しないといけませんし、ディレクトリ構成の変更をしようと思うと大仕事になります。
そこで、import-treeを使用します。これは指定したディレクトリ以下のnixファイルを再帰的にインポートするツールです。
例えば、
modules/
a.nix
subdir/
b.nix
というディレクトリ構成であれば、以下の2つは等価となります。
imports = [ (import-tree ./modules) ];
imports = [
{
imports = [
./modules/a.nix
./modules/subdir/b.nix
];
}
];
これを使うとflake.nixは以下のように書き直せます。
{
description = "My NixOS configuration`";
inputs = {
nixpkgs.url = "github:/nixos/nixpkgs/nixpkgs-unstable";
import-tree.url = "github:vic/import-tree";
};
outputs =
{ nixpkgs, import-tree, ...}:
{
nixosConfigurations.x1 = nixpkgs.lib.nixosSystem {
modules = [ (import-tree ./modules) ];
};
};
}
大変すっきりかけました。
ただ、今後home-managerやdarwinなどを導入した場合、それぞれのモジュールが混在するようになってきます。構成毎に似たようなモジュールを用意してメンテナンスしないといけないので大変です。またアーキテクチャの異なる複数のホストにも対応したいところです。
flake-partsを使うとこうした問題に対応しやすくなります。
flake-partsを使用する
とりあえず適用してみる
ひとまず先のflakes.nixをflake-partsを使して公式ドキュメント に従って書き換えてみます。
{
description = "My NixOS configuration`";
inputs = {
nixpkgs.url = "github:/nixos/nixpkgs/nixpkgs-unstable";
import-tree.url = "github:vic/import-tree";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
};
outputs =
{ flake-parts, import-tree, nixpkgs, ... }@inputs:
flake-parts.lib.mkFlake { inherit inputs; } {
flake = {
nixosConfigurations.x1 = nixpkgs.lib.nixosSystem {
modules = [ (import-tree ./modules) ];
};
};
systems = [ "x86_64-linux" ];
};
}
systemsアトリビュートにはperSystemアトリビュートをビルドする対象のアーキテクチャを記載します。systemsごとにperSystemがビルドされますが、perSystem内ではシステムはconfigを介してアクセスできます。すなわち、
(getSystem "<some architecture>").packages.hello
はperSystem内だと
config.packages.hello
と簡便にかけるようになります。注意が必要なのはここでのconfigはperSystemに渡されるアトリビュートセットのconfigで、トップレベルのconfigとは別物でかつそれをシャドウイングしてしまうという点です2。
ここにはビルドすべきperSystemはないのですが定義しておかないとエラーとなりますので設定しておきます。
このように単一の設定を行うなら導入のメリットは特にないと思います。課題であった複数の対象向けのモジュールの混在ができるよう変更していきます。import-treeはディレクトリ構造に従ってmodules以下すべてのモジュールをインポートしてしまいますので、モジュールのクラス分けを行うためにflake-partsの提供する機能を使います。
flake-parts.flakeModules.modulesによるクラス分け
flake-parts.flakeModules.modulesモジュールの機能
flake-parts.flakeModules.modulesモジュールをインポートします3。
これもモジュールにしてしまいしょう。
modules/以下にディレクトリ
-
fundamentals/システム基幹部分に関連するもの
を追加してflake-parts.nixを配置します。
{ inputs, ... }:
{
imports = [ inputs.flake-parts.flakeModules.modules ];
}
flake-parts.flakeModules.modulesはオプションflake.modulesを宣言します。このオプション以下にアトリビュートとしてモジュールを定義すると、そのアトリビュートがモジュールのclassとなり、config.flake.modules.<クラス名前>でトップレベルからアクセスできるようになります。モジュールは自由に配置可能となり、単一ファイルにNixOSとhome-managerのように異なるクラスの設定を混在させて書くことも可能となります。
ホスト別の設定
まず、複数のホストをカバーできるようにしていきます。ホスト固有の設定モジュールはmodules/hosts/以下に配置します。
hardware-configuration.nixもホスト固有ですので便宜上ここに移動しますが、クラス分けのために直接インポートせずにラッパーを介します。読み込まれないようアンダーバーをつけた名前に変更します。
[~/infra/modules]$ mkdir -p hosts/x1
[~/infra/modules]$ mv hardware-configuration.nix hosts/x1/_hwconf.nix
そしてnixosクラス以下に"hosts/x1"として配置します。
{
flake.modules.nixos."hosts/x1" = { imports = [ ./_hwconf.nix ]; };
}
これでモジュールhardware-configuration.nix(と同値のモジュール)はトップレベルからconfig.flake.modules.nixos."hosts/x1"を介してアクセスすることができるようになります。
もうひとつ、このホストに必要とされる機能のモジュールを記載します。
{
config,
...
}:
{
flake.modules.nixos."hosts/x1" = {
imports =
# Import the nixos modules for the host `x1`.
with config.flake.modules.nixos;
[
base
openssh
# Users
root
naogami
];
};
}
クラス分けに対応したモジュールへの書き換え
先の設定で使ったモジュールを変更していきます。
config.flake.modules.nixos.base以下にはほとんどが共有しそうなモジュールを、config.flake.modules.nixos.openssh以下にはsshのモジュールをおきます。
このホストのユーザーの情報はconfig.flake.modules.nixos.<ユーザー名>とします。
ファイルはmodules/以下どこにおいてもよいのですが、先のディレクトリ構成をそのままつかいます。
nixos.base.boot
loader.nixを変更
他のホストでデュアルブートもできるよう、ブートローダーをgrubに変更します。
{
flake.modules.nixos.base = {
boot.loader = {
efi.canTouchEfiVariables = true;
grub = {
enable = true;
devices = [ "nodev" ];
efiSupport = true;
useOSProber = true;
};
};
};
}
nixos.base.i18n
locale.nixを追加
{
flake.modules.nixos.base = {
i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "ja_JP.UTF-8";
LC_IDENTIFICATION = "ja_JP.UTF-8";
LC_MEASUREMENT = "ja_JP.UTF-8";
LC_MONETARY = "ja_JP.UTF-8";
LC_NAME = "ja_JP.UTF-8";
LC_NUMERIC = "ja_JP.UTF-8";
LC_PAPER = "ja_JP.UTF-8";
LC_TELEPHONE = "ja_JP.UTF-8";
};
};
}
timezone.nixを変更
{
flake.modules.nixos.base = {
time.timeZone = "Asia/Tokyo";
};
}
nixos.base.networking
networking.nixを変更
ホスト名を反映させるように変更します。
hostConfigは後出です。
{
flake.modules.nixos.base =
{ hostConfig, ... }:
{
networking = {
hostName = hostConfig.name;
networkmanager.enable = true;
firewall.enable = true;
};
systemd = {
services.NetworkManager-wait-online.enable = false;
network.wait-online.enable = false;
};
};
}
nixos.base.system
nix.nixを変更
ブログ記事を参考に設定を追加します。
{
flake.modules.nixos.base = {
nix = {
extraOptions = ''
connect-timeout = 5
log-lines = 50
min-free = 128000000
max-free = 1000000000
fallback = true
'';
optimise.automatic = true;
settings = {
trusted-users = [
"root"
];
auto-optimise-store = true;
experimental-features = [
"nix-command"
"flakes"
];
warn-dirty = false;
tarball-ttl = 60 * 60 * 24;
};
};
};
}
state-versions.nixを変更
{
flake.modules.nixos.base = {
system = {
stateVersion = "25.11";
};
};
}
nixos.openssh
openssh.nixを変更
標準的な設定を少し追加します。
{
flake.modules.nixos.openssh = {
services.openssh = {
enable = true;
openFirewall = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = true; # あとで無効化の予定
KbdInteractiveAuthentication = false;
X11Forwarding = false;
};
};
};
}
nixos.users
root.nixを追加
{
flake.modules.nixos.root =
{ pkgs, ... }:
{
users.users.root = {
shell = pkgs.fish;
initialPassword = "id";
};
};
}
naogami.nixを変更
メタ情報を定義して利用していますが後出です。
シェルをfishにしています。
{
config,
...
}:
{
flake = {
meta.users = {
naogami = {
email = "xxx@yyy";
name = "NaoGami";
username = "naogami";
};
};
modules.nixos.naogami =
{ pkgs, ... }:
{
programs.fish.enable = true;
users.users.naogami = {
description = config.flake.meta.users.naogami.name;
isNormalUser = true;
createHome = true;
extraGroups = [
"networkmanager"
"wheel"
];
shell = pkgs.fish;
initialPassword = "id";
};
nix.settings.trusted-users = [ config.flake.meta.users.naogami.username ];
};
};
}
fundamentals
今回の設定の骨格の中心となるものを追加します。
メタ情報を管理する
ユーザー情報などこのflake全体で参照できるメタ情報を定義できるようにオプションflake.metaを宣言します。
meta.nixを追加
{ lib, ... }:
{
options.flake.meta = lib.mkOption {
type = with lib.types; lazyAttrsOf anything;
};
}
nixpkgsを管理する
unfree.nixを追加
nixpkgs.config.allowUnfreePredicateにpredicatorを定義することでunfree packageを許可するか否かパッケージ毎に判定できます。ここでは、ホワイトリストとしてnixpkgs.allowedUnfreePackagesをオプションに宣言しておいて各モジュールから設定しやすくしておきます。
{ lib, config, ... }:
{
options.nixpkgs.allowedUnfreePackages = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
config.flake = {
modules =
let
predicate = pkg: builtins.elem (lib.getName pkg) config.nixpkgs.allowedUnfreePackages;
in
{
nixos.base.nixpkgs.config.allowUnfreePredicate = predicate;
};
meta.nixpkgs.allowedUnfreePackages = config.nixpkgs.allowedUnfreePackages;
};
}
nixpkgs.nixを追加
nixpkgs.allowedUnfreePackagesをパッケージに反映します。
flake-partsのマニュアル推奨に従ってパッケージ定義はperSystemに記載します。systemアトリビュートにはビルドの対象となるアーキテクチャーが定義されてきます。
将来的にはここでoverlayを導入することができます。
{
config,
lib,
nixpkgs,
...
}:
{
perSystem =
{ system, ... }:
let
predicate = pkg: builtins.elem (lib.getName pkg) config.nixpkgs.allowedUnfreePackages;
in
{
_module.args.pkgs = import nixpkgs {
inherit system;
config.allowUnfreePredicate = predicate;
overlays = [ ];
};
};
}
ホストに応じたflake出力を作成する
host-machines.nixの作成
ホスト毎のflake出力を作成します。
nixosクラスに属し"hosts/"をプレフィックスに持つモジュールを抽出し、ホスト毎にモジュールをマージしてnixosConfigurationsに渡します。
Pol Dellaiera氏によるコードで、ここがキモです。
{
inputs,
lib,
config,
...
}:
let
prefix = "hosts/";
in
{
flake.nixosConfigurations = lib.pipe config.flake.modules.nixos [
(lib.filterAttrs (name: _: lib.hasPrefix prefix name))
(lib.mapAttrs' (
name: module:
let
specialArgs = {
inherit inputs;
hostConfig = {
name = lib.removePrefix prefix name;
};
};
in
{
name = lib.removePrefix prefix name;
value = inputs.nixpkgs.lib.nixosSystem {
inherit specialArgs;
modules = [ module ];
};
}
))
];
}
クラス分けに対応したflake.nixへの修正
flake.nixosConfigurations.<ホスト名>はhost-machines.nixで定義されるようになりました。それにあわせてoutputsを変更します。
{
description = "My NixOS configuration`";
inputs = {
nixpkgs.url = "github:/nixos/nixpkgs/nixpkgs-unstable";
import-tree.url = "github:vic/import-tree";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
};
outputs =
{ flake-parts, import-tree, ... }@inputs:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ (import-tree ./modules) ];
systems = [ "x86_64-linux" ];
};
}
ビルドが通ることを確認して叩き台作成は終了となります。