NixOS をインストールする際、通常は NixOS の公式サイトで配布されているライブイメージを USB メモリに焼いてブートし、各種コマンドなどを手動で実行して NixOS をインストールします。ただ、場合によってはこの方法が使えなかったり、使いにくいことがあります。
例えば、最近自分はわくわく鮟鱇ランドのサーバーを KAGOYA Cloud VPS に載せ替えたのですが、KAGOYA は新規に VM を作成する場合はカスタムの ISO イメージを使えるものの、既存の VM を利用して OS を再インストールしたい場合にはそれが使えません[1]。そのため、そもそも NixOS のインストール作業に入ることができません。あるいは物理的なマシンでも、手が届きにくいところに LAN だけ繋がったマシンが置いてあったり、セットアップしたいマシンが大量にあるような場合には、手動でちまちまインストールするのは面倒です。
このようなケースで NixOS をインストールする場合は nixos-anywhere が便利です。
nixos-anywhere は遠隔でつながるマシンに対して NixOS をインストールするためのツールで、あらかじめ configuration.nix などのファイルを用意しておくことで、その設定で NixOS を SSH ごしにインストールできます。内部的には kexec を活用してカーネルのスイッチを行います。そのため、NixOS がインストールされる側のマシンは SSH ごしに(root で繋がって kexec が使える)Linux が露出されていればよく、適当な Linux distro のライブ USB がブートされただけの状態でも問題ありません[2]。
nixos-anywhere 公式の Quickstart Guide が丁寧に書かれているので、これを見ると使い方はおおよそわかります。以下は KAGOYA Cloud VPS で nixos-anywhere を使った際の記録です。予め KAGOYA の Web UI から「初期化」ボタンを押し、ターゲットの VM には AlmaLinux 9 をインストールしてあります。
まず Nix Flake の依存に disko を追加します。disko は nixos-anywhere が前提にするツールで、ディスクのセットアップ(パーティションやLVM の設定など)を行います:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05-small";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {self, nixpkgs, disko}: {
nixosConfigurations = {
kagoya = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [./configuration.nix disko.nixosModules.disko];
};
};
};
}
続いて configuration.nix を用意します。基本的に nixos-anywhere の example コードをそのまま使えば良いですが、状況に合わせて修正も必要です。KAGOYA の場合、IP アドレスは固定なので、それを前提にしたネットワーク設定を盛り込んでおく必要があります。この辺りは以前書いた記事を参考にしてください。
{lib, pkgs, ...} @ args:
{
imports = [
./disk-config.nix # 後で作る
./hardware-configuration.nix # nixos-anywhere が自動生成する
];
# KAGOYA は BIOS のみの対応なので nixos-anywhere を使う場合
# boot.loader.grub は空で良い。
#boot.loader.grub = {
# # no need to set devices, disko will add all devices that have a EF02 partition to the list already
# # devices = [ ];
# efiSupport = true;
# efiInstallAsRemovable = true;
#};
services.openssh.enable = true;
# 固定 IP アドレス用の設定を書く
systemd.network = {
enable = true;
networks."10-wan" = {
# インターネットに出ていける NIC
matchConfig.Name = "eno1";
# 割り当てられた IP アドレスとサブネットマスク
address = ["198.51.100.100/24"];
routes = [
{
# デフォルトゲートウェイ
Gateway = "198.51.100.1";
}
];
linkConfig.RequiredForOnline = "routable";
};
};
networking = {
hostname = "kagoya";
useDHCP = false; # DHCP を無効化
defaultGateway = "198.51.100.1"; # デフォルトゲートウェイ
# DNS サーバ
nameservers = ["198.51.100.200" "198.51.100.201"];
};
environment.systemPackages = map lib.lowPrio [
pkgs.curl
pkgs.gitMinimal
];
users.users.root.openssh.authorizedKeys.keys =
[
# 手元のマシンの公開鍵を設定しておく。
"ssh-ed25519 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX your-system"
] ++ (args.extraPublicKeys or []); # this is used for unit-testing this module and can be removed if not needed
system.stateVersion = "26.05";
}
さらに imports で読み込むための disk-config.nix を作ります。これは disko の設定ファイルで、ディスクをどのようにセットアップするかを指定します。disko の example ディレクトリに様々なケースでどのように書くべきかという例があるので、基本的にはこれを見ればなんとなく雰囲気がわかります。今回は gpt-bios-compat.nix を参考に以下のようにしました:
{
disko.devices = {
disk = {
main = {
device = "/dev/vda";
type = "disk";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for grub MBR
attributes = [ 0 ]; # partition attribute
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
}
これで用意すべきファイルは全てなので nix flake lock して flake.lock を更新した後、以下のように nixos-anywhere を実行します:
nix run github:nix-community/nixos-anywhere -- \
--generate-hardware-config nixos-generate-config ./hardware-configuration.nix \
--flake .#kagoya \
--target-host root@(VM の IP アドレス)
セットアップが正しければ、これで接続先のホストに NixOS がインストールされます。ダメだった場合は KAGOYA の Web UI から AlmaLinux 9 を再インストールして再挑戦しましょう(1 敗)[3]。