NixOSで最強のLinuxデスクトップを作ろう

2023/07/09に公開
14

Web・クロスプラットフォームアプリケーションの発達、ProtonによるWindows向けゲームのLinux対応など、現在のLinuxデスクトップは機能面においてWindowsやMacと遜色ない水準に達しています。

しかし、その発展とは裏腹に未だシェアは非常に少なく、Linuxデスクトップに憧れはあるけど実際に使うのはあまり……という人が多数派でしょう。その理由はおおよそ以下の3つに集約されると思います。

  1. 環境構築のために煩雑な手順を踏まなければならず辛い
  2. どこにどんな設定がされているのか分からない
  3. うっかり環境を破壊してしまいそうで怖い

実は、上記の課題を上手くクリアできる夢のようなLinuxディストリビューションが存在します。
その名は……

NixOS
ロゴかっこいい

今回はNixOSを使ってぼくがかんがえたさいきょうのLinuxデスクトップ環境を作っていきましょう。

NixOSとは?

NixOS: A Purely Functional Linux Distribution

NixOSは、純粋関数型パッケージマネージャであるNixをベースにしたLinuxディストリビューションです。NixOSではなんと環境構築を完全に宣言的に行うことができます。つまり、望む環境の状態をコードで記述してNixOSに処理させることで、自動的にパッケージのインストールやシステムの設定などを行ってくれるのです。

「え、自動インストールに自動設定?依存関係とか大丈夫?🤔」
と疑問に思った方へ、問題ありません。NixOSの環境構築はNixによるビルドという形で行われます。Nixは強力なビルドシステムを持っており、非常に高い再現性を誇ります[1]。NixOSでは依存関係に関する問題がほとんど発生しません。

実際に私が過去に行った環境再構築RTAの記録が以下です[2]

約20分となっていますが、そのほとんどはダウンロード・インストール処理の時間で、実際にコマンド操作を行った時間は5分もありません。

興味が湧いてきませんか?
NixOSを知るにはまずパッケージマネージャNixを知る必要があります。詳しく見ていきましょう。

Nix

Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible.

Nix』はラテン語で『雪の結晶』を意味します。パッケージマネージャとしては以下のような特徴を持ちます。

  • 高い再現性
  • パッケージ数8万超の公式パッケージリポジトリnixpkgs
    • 安定版ブランチ・ローリングリリースブランチが存在
  • UNIX系システム(Linux全般、macOS)で利用可能

Nix自体はNixOSとは独立した汎用的なツールです。

Nixは単なるパッケージマネージャではなく、独自のビルドシステムやProfilesと呼ばれる環境構築機能などが合わさった複合的なソフトウェアです。NixOSの機能は、従来Nixが備える機能を拡張するような形で実現されています。本文では以下の3つについて重点的に説明します。

  1. DerivationとNix store
  2. Nix言語
  3. Profiles
  4. Flakes

順に見ていきましょう。

DerivationとNix store

Nixのビルドシステムは、パッケージを関数型言語の値のように扱います。つまり、副作用のない参照透過な関数から生成され、生成物はイミュータブルに扱われることを意味します。

Nixでパッケージをビルドすると、/nix/storeディレクトリ下に、以下のようなハッシュ値を名前に含んだサブディレクトリが作成され、その下にビルド成果物が格納されます。

Gitバージョン2.41.0
                                          Package
                                            ┌┴┐
/nix/store/y0gvg44jdsbn8hnnr27ixjf102nk7a9x-git-2.41.0/
           └──────────────┬───────────────┘     └──┬──┘
                        Hash                    Version

Nixは、パッケージをビルドするためにDerivationと呼ばれるものを使用します。
Derivationとは、簡単に言うとパッケージの非常に厳密なレシピです。Derivationはパッケージを同定する全ての要素(依存関係、ソースコード、ビルドスクリプト、環境変数、シェル、システムアーキテクチャなど)を入力として扱います。この入力をもとハッシュ計算を行い、パッケージの識別子としてディレクトリ名に付加しているのです。1bitでも入力が異なれば全く異なったハッシュ値になるため、パッケージは厳密に区別されます。

例として、Hello, World!をコンソールに表示するRustのプログラムのDerivationを記載しておきます。長いので折りたたんでいます。

hello-rsのDerivation

実際のDerivationは.drvファイルとしてNix storeに格納されており、以下はnix derivation showコマンドでJSON形式にPretty-printしたものです。

  • builder: ビルドを実行するシェル
  • env: 環境変数
  • inputDrvs: 依存関係(Derivation)
  • inputsSrcs: ソース
  • outputs: ビルド成果物を配置するディレクトリ
  • system: システムアーキテクチャ

依存関係としてinputDrvs/nix/store/9pvlx7xb9h7xbvmfmkvp54i8sa34pa6f-rustc-1.70.0.drv(RustコンパイラのDerivation)などが指定されています。

{
  "/nix/store/1b6b4lh490crvsdypk1nnv1qa6w3hzm5-hello-rs.drv": {
    "args": [
      "-e",
      "/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh"
    ],
    "builder": "/nix/store/51sszqz1d9kpx480scb1vllc00kxlx79-bash-5.2-p15/bin/bash",
    "env": {
      "PKG_CONFIG_ALLOW_CROSS": "0",
      "__structuredAttrs": "",
      "buildInputs": "",
      "builder": "/nix/store/51sszqz1d9kpx480scb1vllc00kxlx79-bash-5.2-p15/bin/bash",
      "cargoBuildFeatures": "",
      "cargoBuildNoDefaultFeatures": "",
      "cargoBuildType": "release",
      "cargoCheckFeatures": "",
      "cargoCheckNoDefaultFeatures": "",
      "cargoCheckType": "release",
      "cargoDeps": "/nix/store/w7grdh3xyk94ahc4q2jh0rjlgcpsj981-cargo-vendor-dir",
      "cmakeFlags": "",
      "configureFlags": "",
      "configurePhase": "runHook preConfigure\nrunHook postConfigure\n",
      "depsBuildBuild": "",
      "depsBuildBuildPropagated": "",
      "depsBuildTarget": "",
      "depsBuildTargetPropagated": "",
      "depsHostHost": "",
      "depsHostHostPropagated": "",
      "depsTargetTarget": "",
      "depsTargetTargetPropagated": "",
      "doCheck": "1",
      "doInstallCheck": "",
      "mesonFlags": "",
      "name": "hello-rs",
      "nativeBuildInputs": "/nix/store/mkplk6shr1lvy3w0n2hpmkvv4rvkqa70-auditable-cargo-1.70.0 /nix/store/xv57wrly4ixy2d7lzajixfhzhyx54cbx-cargo-build-hook.sh /nix/store/9cd8rx3xfbqzihqwzfncbsn3bzqzdz7r-cargo-check-hook.sh /nix/store/1nq92m0mn1k9a8lbx6jcj00dbdw48j01-cargo-install-hook.sh /nix/store/3fk9yaj769bn5g6557xj512m8qix0h14-cargo-setup-hook.sh /nix/store/rdxwgdyfj0sa7sz6p4c6fp3irlhnwixn-rustc-1.70.0",
      "out": "/nix/store/hgaxjl9qvcla891yg6ipzphnl6sgj0hw-hello-rs",
      "outputs": "out",
      "patchRegistryDeps": "/nix/store/nk6b2ckznjic5wj8ddw0wgdrn4mbz3lg-patch-registry-deps",
      "patches": "",
      "postUnpack": "eval \"$cargoDepsHook\"\n\nexport RUST_LOG=\n",
      "propagatedBuildInputs": "",
      "propagatedNativeBuildInputs": "",
      "src": "/nix/store/xzl1xr811d6s1n4dgnk0xw8vrvsm6j0b-12v8dj6mcqpmrlsskcfd283sgi99zqpf-source",
      "stdenv": "/nix/store/blpvf60m29q02c0lc5fyhim30ma4y1vv-stdenv-linux",
      "strictDeps": "1",
      "system": "x86_64-linux"
    },
    "inputDrvs": {
      "/nix/store/29yjg4ilzpdwh4m45lv6c4m5v2lppsn2-bash-5.2-p15.drv": ["out"],
      "/nix/store/5yzp580n67ikaz10fylp82x97z8g8rni-cargo-check-hook.sh.drv": [
        "out"
      ],
      "/nix/store/9pvlx7xb9h7xbvmfmkvp54i8sa34pa6f-rustc-1.70.0.drv": ["out"],
      "/nix/store/fgc4aafdrqczbdga0fp5kds7r01ka28x-stdenv-linux.drv": ["out"],
      "/nix/store/gq69b4678qsswsqhfs3yzxla1qfxp1m9-cargo-setup-hook.sh.drv": [
        "out"
      ],
      "/nix/store/l8h3lmhbxvhndjb88qf5b84wk01f2xdj-cargo-vendor-dir.drv": [
        "out"
      ],
      "/nix/store/vmkhqrbba05vvz8gr5sc93yggkhrd0fk-cargo-install-hook.sh.drv": [
        "out"
      ],
      "/nix/store/zpi7glnyk7dwcbjwrnf970xh5466lwi8-cargo-build-hook.sh.drv": [
        "out"
      ],
      "/nix/store/zzax3dvxcll8aq0qw23phjxlim727hhi-auditable-cargo-1.70.0.drv": [
        "out"
      ]
    },
    "inputSrcs": [
      "/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh",
      "/nix/store/nk6b2ckznjic5wj8ddw0wgdrn4mbz3lg-patch-registry-deps",
      "/nix/store/xzl1xr811d6s1n4dgnk0xw8vrvsm6j0b-12v8dj6mcqpmrlsskcfd283sgi99zqpf-source"
    ],
    "name": "hello-rs",
    "outputs": {
      "out": {
        "path": "/nix/store/hgaxjl9qvcla891yg6ipzphnl6sgj0hw-hello-rs"
      }
    },
    "system": "x86_64-linux"
  }
}

複数バージョンの共存

所謂Dependency Hellとは、複数のソフトウェアがそれぞれ同じパッケージの異なるバージョンに依存することで発生します。依存先のバージョン間で破壊的な変更が入ると、依存していたソフトウェアのどちらかが機能しなくなる可能性があります。

Nixで異なるバージョンのパッケージをインストールするとDerivationの入力が変わるため、必然的に出力されるハッシュ値が変わり、別々のディレクトリに配置されます。

以下は私の環境のNix store内にあるopensslの一部です。複数のバージョンが共存していることがわかります。
バージョンが被っているものがありますが、Nixでは完全に入力が一致しない限りそのパッケージは別物扱いとなります。Nixにおいてパッケージのバージョンは人間向けのセマンティクスでしかありません。

openssl
h7zllwcidvkg8i5v6hkf49n3sd5lq290-openssl-3.0.5/
hggh523whplc4bk681c95r52bsjc2wb5-openssl-3.0.9/
ii5lq0igwk9xpq16s98yqswsgr1dbfi2-openssl-3.0.9/
ijk9j536zs30kha06rr966gplwxd7fbg-openssl-3.0.8/
ip9kk8kla5bff32mqjmwdn29sbhyd19c-openssl-3.0.8/
iw4cmla0978f1lgn23lmqmra3lrfwd4a-openssl-3.0.9/

暗黙的依存の排除

Derivationにおいて重要なのが、入力以外の要素はビルドに影響を与えることができないということです。NixはDerivationで指定されたシェルに、同じく指定された依存関係や環境変数を導入した純粋な環境を立ち上げます。そこでビルドスクリプトを実行することでビルドが完了します。

もし、とあるパッケージが開発環境にグローバルにインストールされているパッケージX依存していて、開発者が依存関係にパッケージXを指定し忘れたとしても、Nixの純粋なビルド環境にはパッケージXが導入されないためビルドが失敗します。つまり、全ての依存関係が陽の下に扱われ、暗黙的依存が排除されます。

バイナリキャッシュ

Nixはパッケージインストール時に必ずDerivationを評価するため、本来なら常にローカルでビルドが走って膨大な時間がかかるはずですが、実際のインストールは爆速です。
実はNixの公式パッケージリポジトリnixpkgsでは、HydraというCIシステムで常時パッケージのビルド結果をキャッシュしています。

  1. パッケージリポジトリからNix式を取得(後述)
  2. Derivationを生成
  3. Derivationのハッシュ値をHydraのキャッシュに照会
  4. キャッシュが存在する場合、ビルド済みバイナリを直接ダウンロード

Nixのビルドシステムによって、ハッシュが同一であればビルド結果も同一になることが保証されているので、再現性を損なわずインストールを高速化できます。

Nix言語

Derivationは直接人が読み書きするものではなく、NixのDSLであるNix言語によって記述されたNix式を用いて生成します。以下は、前述のhello-rsパッケージをビルドするためのNix式です。

default.nix
{ pkgs ? import <nixpkgs> { }, ... }: {
  hello-rs = pkgs.rustPlatform.buildRustPackage {
    name = "hello-rs";
    src = ./.;
    cargoLock = { lockFile = ./Cargo.lock; };
  };
}

Nix言語では、1つのファイルが1つの関数式となっています。

{引数}: 出力

pkgs ? import <nixpkgs> { }は、pkgsが引数として与えられない時(なぜこんな回りくどいことをするのかは後述)、nixpkgsをインポートします。<nixpkgs>はローカルにダウンロードされたnixpkgsへのファイルパスを示します。nixpkgsの実体はNix式のセットです。

今回の出力はkey = vlue;形式で列挙されており、このデータ構造をAttribute Setと呼びます。上記の出力のAttribute Setは、hello-rsというkeyに、パッケージのDerivationを入れています。
Derivationを作るためにpkgs.rustPlatform.buildRustPackageというユーティリティ関数を使っています。このようにpkgs(=nixpkgs)はNix言語のライブラリでもあります。

あくまでNix式は単なるデータを出力しているに過ぎないので、これ単体では何もできません。コマンドがNix式を評価して初めてパッケージがビルドされます。

ビルドコマンド
nix-build

実際にビルドしてみるとresultというシンボリックリンクが実行したディレクトリ配置されます。リンク先はNix store内のビルド成果物の実体です。

$ readlink ./result
/nix/store/hgaxjl9qvcla891yg6ipzphnl6sgj0hw-hello-rs

nix-shell

Nixでは、Derivationを用いて、パッケージ以外にも様々なものをビルドすることができます。

以下のNix式を用意します。先程のNix式からhello-rsのDerivationをインポートし、let in構文でhello-rsとして宣言しています。

shell.nix
{ pkgs ? import <nixpkgs> { }, ... }:
let
  hello-rs = (import ./default.nix pkgs).hello-rs;
in
pkgs.mkShell {
  buildInputs = [
    hello-rs
  ];
}

nix-shellコマンドでshell.nixを評価します。

bash-5.1# nix-shell
# パッケージのビルドが走る

[nix-shell:~/hello-rs]# hello-rs
Hello, world!

[nix-shell:~/hello-rs]# exit
exit

bash-5.1# hello
bash: hello: command not found

一時的にパッケージのパスが通されたシェル環境が立ち上がります。開発環境を用意するのに非常に便利な機能です。

Profiles

実際にパッケージを使用できるようにするためには、コマンドのPATHを通したり、適切な場所にファイルを配置したりといったインストール処理が必要になります。Nixはパッケージの実体ファイルを/nix/storeで一元管理し、必要な位置にシンボリックリンクを配置することでこれを実現しています[3]

Nixは/usr/binなどのグローバルなディレクトリを使わず、~/.nix-profile下にシンボリックリンクを配置します。中身は以下のようになっていて、よくみるディレクトリ構造になっています。

$ ls ~/.nix-profile
bin  etc  lib  libexec  manifest.nix  sbin  share

NixでインストールしたcurlのPATHをみてみると以下のようになっています。~/.nix-pforile/bin内のシンボリックリンクにPATHが通されています。

$ echo $PATH
/root/.nix-profile/bin:(中略)

$ which curl
/root/.nix-profile/bin/curl

$ readlink $(which curl)
/nix/store/52fbv6j49khca4cfvwm35fqd984w2520-curl-7.86.0-bin/bin/curl

また、.nix-profile自体もNixによってビルドされており、シンボリックリンクになっています。

$ readlink ~/.nix-profile
/nix/var/nix/profiles/default

.nix-profileの実体ファイルが配置されている場所には複数のProfileが配置されています。しかも全てシンボリックリンクになっており、.nix-profileの中身と同じ構造をしています。

$ readlink ~/.nix-profile
/nix/var/nix/profiles/default

$ ls -l /nix/var/nix/profiles/
default -> default-4-link
default-1-link -> /nix/store/d8c5588zaaylx31hax59j4pjj9pcik88-user-environment
default-2-link -> /nix/store/15nriql44lwh2dw9q5x8p22kaa882maz-profile
default-3-link -> /nix/store/hf0yc0syc71y9yryn9qqyhsq4i7kj0hi-profile
default-4-link -> /nix/store/15nriql44lwh2dw9q5x8p22kaa882maz-profile
per-user

Profilesの仕組みは以下のようになっています。Nixはパッケージに対する操作(インストール、アンインストール、アップデート)が実行される度に新しいProfileをビルドし、defaultにリンクします。

画像
引用: Profiles - Nix Reference Manual

グローバルを汚染しない

Nixで管理されるものは全て閉じた環境に隔離されるため、もし他のパッケージマネージャが存在したとしても既存の環境と競合しません。NixOS以外のシステムでも安全に導入することができます。

Profileのロールバック

Profileの操作は常に非破壊的なので、過去のProfileは上書きされずにそのまま残ります。これを利用してdefaultのリンク先を任意の世代のProfileに切り替えることで、環境をロールバックすることができます。

Profilesにおけるインストール/アンインストール

Nixにおいて一般的な意味でのアンインストールコマンドは存在しません。実際それに相当するコマンドを実行すると、該当するシンボリックリンクが除かれたProfileが新しくビルドされるだけで、実体ファイルはNix storeに残り続けます。
そのままではストレージが枯渇してしまうので、Nixにはガベージコレクション機能が搭載されています。期間を設定すると自動的にProfileへリンクされていないNix store内のパッケージを削除します。コマンドでGCを手動実行することも可能です。

Flakes

実は、Nix言語によるパッケージビルドはいくつかの問題を抱えています。

  1. ローカルのパッケージセットに依存している
  2. インターフェースが非統一

パッケージセットの種類・バージョンはマシンごとに異なる可能性があります。先程例に挙げたNix式ではpkgsimport <nixpkgs> {}でローカルのパッケージセットをインポートしており、実行環境ごとにビルド結果が変わってしまいます。
また、nix-buildnix-shellなど機能ごとにコマンドが分かれていますが、Nix言語のセマンティクスには反映されません。これでは用途ごとに複数のエントリポイントを持つことになり扱いにくいです。

これらを解決するのがNixのプロジェクト管理機能であるFlakesです。FlakesはGitとの併用を前提としており、Gitリポジトリのルートにflake.nixというファイルを配置することで利用できます。

Nix channels

Flakesに対して、古いNixのパッケージリポジトリ管理機構はChannelsと呼ばれます。Channelsは、パッケージリポジトリを購読し、ローカルのパッケージセットを自動更新します。コマンドからnixpkgs以外のパッケージリポジトリを登録・購読することも可能です。再現性を追求するために現在ではFlakesが主流です。

flake.nix

flake.nixの構造は単純で、依存する外部のFlake(flake.nixで管理されているGitリポジトリ)を指定するinputsと任意のNix式を定義できるoutputsしかありません。

flake.nix
{
  inputs = {
    # 依存するFlakeを指定
  };
  outputs = inputs: {
    # 任意のNix式をここに定義
  };
}

inputs

Flakesにデフォルトのパッケージリポジトリは存在しません。依存関係としてパッケージリポジトリを全て明示的に指定する必要があります。
nixpkgsを利用したいなら以下のように記述します。

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs";
  };
  outputs = inputs: {};
}

nixpkgs、正確にはnixpkgsのFlakeをインポートしています。GitHubリポジトリはgithub:<オーナー>/<リポジトリ>/<ブランチ>のフォーマットで指定できます。Flakesユーザーが自身のプロジェクトを公開したいときはGitHubにFlakeをアップするだけでいいのです。

また、Flakesはflake.lockファイルによってGitのコミットハッシュでバージョンをロックします。コマンドでflake.nixを評価するとその時点で最新のinputsのFlakeが取得され、flake.lockが生成されます。

flake.lockの中身とコミットハッシュ

flake.locknodes.nixpkgs.revとnixpkgsのリポジトリのコミットハッシュが同一なことが確認できます。

flake.lock
{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1688789517,
        "narHash": "sha256-2UpFTJ/bkWCs9Cs7ocko10U7b40VaI5+x57LDun52q4=",
        "owner": "nixos",
        "repo": "nixpkgs",
+       "rev": "d4d822f526f1f72a450da88bf35abe132181170f",
        "type": "github"
      },
      "original": {
        "owner": "nixos",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

outputs

outputsinputsを引数にとる関数式です。出力にはパッケージ、開発用シェル、テンプレート、NixOS modueles(後述)など、色んなものを指定できます。指定可能なものはFlakes - NixOS Wikiに列挙されています。

hello-rsのflake.nix
outputs = inputs: {
  packages."<system>"."<name>" = derivation;
  devShells."<system>"."<name>" = derivation;
  # templates, apps, formatter, overlays, nixosModules, etc...
};

例えば、packagesnix build, nix shell, nix run, 、devShellsnix developで利用できます。これで複数の用途に対してエントリポイントが1つになりました。

Nix command

nix <subcommand>の形で提供されるコマンド体系はNix commandと呼ばれます。Nix commandは内部でFlakesを利用するようになっており、Flakesの依存関係管理によってより強固な再現性を担保します。

nix build

nix-buildのNix command版です。outputspackagesを評価します。
試しにhello-rsをFlake化して利用してみます。以下のflake.nixを作成しました。
inputsのnixpkgsをインポートしてpkgsに束縛しています。packagesには例のNix式をインポートしています。pkgs ? import <nixpkgs> {}なんて回りくどい書き方をしたのは、非Flakesベースのレガシーなコマンドでも評価できるようにするためです。

hello-rsのflake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs";
  };
  outputs = inputs:
  let
    pkgs = import inputs.nixpkgs {
      system = "x86_64-linux";
    };
  in
  {
    packages."x86_64-linux" = import ./default.nix pkgs;
  };
}

以下のコマンドでビルドします。FlakesはGitを介してファイルを追跡するので、ビルドに関わるファイルはステージかコミットされている必要があります。
Nix commandの引数は<Flakeの場所>#<名前>で与えます。

git add .
nix build .#hello-rs

リモートのFlakeも参照できます。flake.nixinputsのurlの記法と同じフォーマットでFlakeを指定できます。

nix build github:nixos/nixpkgs#git

nix run

nix build同様、packagesを評価しますがこちらはビルド完了後に一度だけ実行して終了します。resultディレクトリは作成されません。

nix shell

nix-shellのNix command版です。nix-shellには、shell.nixを用意する方法とnix-shell -p <パッケージ名>で直接パッケージを指定する方法の2つがあり、Nix commandでは前者がnix develop、後者がnix shellに分離されています。

nix develop

devShellを評価します。非常に便利な開発環境構築機能です。
以下のNix式を用意し、nix developコマンドを実行するとDenoが導入されたbashが立ち上がります。

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/master";

  outputs = inputs: let
    pkgs = import inputs.nixpkgs {
      system = "x86_64-linux";
    };
  in {
    devShells."x86_64-linux".default = pkgs.mkShell {
      buildInputs = with pkgs; [
        deno
      ];
    };
  };
}

また、.envrcファイルがあるディレクトリに移動すると自動で環境変数を設定してくれるdirenvと併用すると、ディレクトリを移動するだけで勝手に開発環境が立ち上がる素晴らしい開発体験を得ることができます(nix-direnvが必要)。

echo "use flake" >> .envrc
direnv allow # direnv有効化

NixOSの機能

NixOSはNixのProfilesを拡張し、root権限が必要なシステムレベルの設定をProfilesで行えるようにしています。ユーザー環境のProfileのであるprofileとは別にsystemというProfileを作成します。

$ ls /nix/var/nix/profiles/system
activate  append-initrd-secrets  bin  boot.json  dry-activate  etc
extra-dependencies  firmware  init  init-interface-version  initrd
kernel  kernel-modules  kernel-params  nixos-version  specialisation
sw  system  systemd

/etcに配置される各種設定ファイル、カーネルモジュール、サービス、システムレベルのコマンドなどがシンボリックリンクで配置されています。

NixOS modules

NixOSの最大の特徴であり、非常に便利な機能です。私はこれなしにLinuxを使うことができなくなりました。

NixOS modulesはNix言語を用いて環境を宣言的に記述する機能です。
例として、設定が面倒なIMEとフォントに関するモジュールを示します。

{pkgs, ...}: {
  i18n.inputMethod = {
    enabled = "fcitx5";
    fcitx5.addons = [pkgs.fcitx5-mozc];
  };

  fonts = {
    fonts = with pkgs; [
      noto-fonts-cjk-serif
      noto-fonts-cjk-sans
      noto-fonts-emoji
      nerdfonts
    ];
    fontDir.enable = true;
    fontconfig = {
      defaultFonts = {
        serif = ["Noto Serif CJK JP" "Noto Color Emoji"];
        sansSerif = ["Noto Sans CJK JP" "Noto Color Emoji"];
        monospace = ["JetBrainsMono Nerd Font" "Noto Color Emoji"];
        emoji = ["Noto Color Emoji"];
      };
    };
  };
}

これを書いてnixos-rebuild switchコマンドを実行すると、日本語IMEが有効化され、日本語フォント(Noto Fonts)がデフォルトフォントに設定されます。

ロールバック機能

再現性が高く、宣言的に記述できるといっても、誤った設定を書いてシステムを破壊する可能性は依然存在します。
でも大丈夫!NixOSはシステム環境もロールバックすることができます。NixOSでは前世代のProfileを使ってなんとブートローダーから環境をロールバックすることができます。

NixOSのロールバック
引用: Guides - How Nix Works - NixOS

設定でやらかしてLinuxが起動しないような事態に陥っても、再起動してブートローダーから安全な世代を読み込めば即座に復旧可能です。

システムの破壊?知るか!俺はNixOSだぞ!

OSインストール

お待たせしました。ここからは手を動かしていきましょう!

NixOSにはグラフィカルインストーラーがあるのでそれを使っていきます。以下のリンクからISOをダウンロードしてUSBに焼いてください。ventoyというツールがおすすめです。

ミニマルISOでのインストール

コンソールからインストールしたい方はMinimal ISO imageをダウンロードしてください。インストールマニュアルのInstalling NixOSManual Installationに全ての手順が書いてあります。
手順に従ってインストールした後、作成したユーザーにpasswdでパスワードを設定するのを忘れないでください。

Windowsとのデュアルブート
  1. Windowsの設定からBitLocker暗号化を解除
  2. BIOS/UEFIからSecure Bootを無効化
  3. インストーラー起動後、GpartedでWindowsストレージをリサイズ

BitLocker暗号化が有効化されているとセキュアブート無効化後にWindowsにログインすると回復キーの入力を求められて面倒になるので切っておきます。ストレージのリサイズについて、Windowsの標準ツールでは断片化したストレージを縮小できないので、NixOSのインストーラーに内包されているGpartedで縮小しましょう。

NixOS installer

ブートローダーから一番上の項目を選択して起動するとこのような画面が現れます。Linuxデスクトップにおいては一般的なインストーラです。

NixOS installer

インストーラーから必要な項目を設定していきましょう。インストーラーを日本語にします。

NixOS installer

ロケールを選びます。私はシステムの言語は英語(en_US.utf-8)にしていますが、一応ここでも日本語にしておきます。数値と日付のロケールに関しては、コマンドで日時が○月○日みたいに表示されると気持ち悪いのでAmerican Englishにしておきます。

Location

キーボードの配列です。私はUS配列を使っているのでそれを選びました。下の入力欄でタイプしてチェックできます。

Keyboard

ユーザー名とパスワードを設定します。

User

デスクトップ環境を選びます。お好みでいいですが、こだわりがないならインストーラーと同じGNOMEにしておきましょう。NixOS modulesを用いて後から簡単に変更できるので特に気にしなくても大丈夫です。

Desktop

プロプライエタリなパッケージを許可するかどうかの質問です。後述の設定でまとめて行うので今はチェックしません。

Unfree Software

インストールするパーティションを選択します。

Partition

設定事項を確認してインストールを実行します。40%あたりでプログレスバーがしばらく止まるかもしれませんが、スマホでNixのマニュアルでも読みながら気長に待ちましょう。

Install

終わりました!再起動しましょう。

All done

ブートローダーが起動するので、NixOSを選択しましょう。初期設定ではホストがUEFIの場合はsystemed-boot、BIOSならGrubがブートローダーになっています。。

Boot Loader
systemed-boot

ログイン画面が現れたら設定したユーザー名とパスワードでログインしましょう。

環境構築

コンソールを開いて以下のパスを確認します。

$ ls /etc/nixos/
configuration.nix hardware-configuration.nix

この2つのファイルはインストール時に自動生成されたものです。hardware-configuration.nixはマシンのハードウェア関連の設定が記述されているため、触らず初期設定のまま利用する方針でいきます。
本題はconfiguration.nixの方で、こちらをバンバン書き換えていきます。

まず、nanoを使って以下の設定を付け足します。/etcの配下なのでsudoが必要です。

sudo nano /etc/nixos/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
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, ... }:

{
   # 各種設定...

+  nix = {
+    settings = {
+      experimental-features = ["nix-command" "flakes"];
+    };
+  };
}

書き換えたらビルドします。

sudo nixos-rebuild switch

再起動が必要な設定ではないため、このまま作業を継続します。

上記の設定でNix commandとNix Flakesが有効化されました。
試しにシステム情報を表示するneofetchを使ってみましょう。

nix run github:nixos/nixpkgs/nixpkgs-unstable#neofetch

neofetch

最初に37.9MBほどのダウンロードが走りますが、これはパッケージ本体ではなく、指定したパッケージリポジトリから最新のパッケージセット(Nix式のセット)をダウンロードしています。常に実行時点で最新のリポジトリを参照するため、リポジトリに更新が入る度にダウンロード処理も毎回実行されます。

github:nixos/nixpkgs/nixpkgs-unstableはよく使われるため、nixpkgsというAliasが設定されています。以下のコマンドは先程のものと等しいです。

nix run nixpkgs#neofetch

Aliasの一覧は以下のコマンドで確認できます。

nix registry list

Flake化

Flakesを有効化したので、設定をFlake化しましょう。
configuration.nixhardware-configuration.nixをコピーしてGitリポジトリを作ります。

mkdir ~/.dotfiles && cd ~/.dotfiles
cp /etc/nixos/* .
git init

flake.nixを作ります。nix shellで好きなエディターを導入して編集しましょう。
nixpkgsはローリングリリースのnixpkgs-unstableブランチを選びました。outputsにはnixosConfigurationsで定義し、Derivationはnixpkgs.lib.nixosSystemというユーティリティを利用します。

flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  };

  outputs = inputs: {
    nixosConfigurations = {
      myNixOS = inputs.nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
      };
    };
  };
}

ビルドコマンドは以下のように変わります。

sudo nixos-rebuild switch --flake .#myNixOS

シェル環境の整備

configuration.nixを以下のように書き換えてみます。デフォルトのシェルをzshに変更し、諸々のパッケージを導入します。

{ config, pkgs, ... }:
{
  # 各種設定...

  users.users.ユーザー名 = {
    # ...
+    shell = pkgs.zsh; # デフォルトのシェルをZSHに変更
  };

  # ...

+  # 設定込みでパッケージを有効化
+  programs = {
+    git = {
+      enable = true;
+    };
+    neovim = {
+      enable = true;
+      defaultEditor = true; # $EDITOR=nvimに設定
+      viAlias = true;
+      vimAlias = true;
+    };
+    starship = {
+      enable = true;
+    };
+    zsh = {
+      enable = true;
+    };
+  };
}

environment.systemPackagesで指定したパッケージはシステム全体にインストールされます。ユーザー単位でインストールしたい場合はusers.users.ユーザー名.packagesを使います。

programsはパッケージをただインストールするだけでなく、諸々のコンフィグを自動設定します。
例えば、bashやzshでstarship(良さげな見た目のプロンプト)を有効化するには、本来ならいくつかの設定を手動で行う必要があるのですが、NixOS moodulesではprograms.starship.enable = trueだけで勝手に設定してくれます。
ビルド後、新しいコンソールのタブを開くと見た目がイケてるzshが起動します。

IME・日本語フォントの有効化

大抵の場合、Linuxでこれらを設定するのはかなり面倒なのですが、NixOS modulesでは以下の設定だけで済みます。
ビルド後、再起動してください。Noto Fontsが適用された美しいUIが立ち上がり、IMEが有効化されます。

{ config, pkgs, ... }:
{
  # 各種設定

+ i18n.inputMethod = {
+   enabled = "fcitx5";
+   fcitx5.addons = [pkgs.fcitx5-mozc];
+ };

+ fonts = {
+   fonts = with pkgs; [
+     noto-fonts-cjk-serif
+     noto-fonts-cjk-sans
+     noto-fonts-emoji
+     nerdfonts
+   ];
+   fontDir.enable = true;
+   fontconfig = {
+     defaultFonts = {
+       serif = ["Noto Serif CJK JP" "Noto Color Emoji"];
+       sansSerif = ["Noto Sans CJK JP" "Noto Color Emoji"];
+       monospace = ["JetBrainsMono Nerd Font" "Noto Color Emoji"];
+       emoji = ["Noto Color Emoji"];
+     };
+   };
+ };
}

ドライバ・キーリマップ

nixos-hardwareはハードウェア関連の設定をよしなにやってくれるモジュールのコレクションです。Intel, AMD, NVIDIAなどのドライバの設定と、Raspberry PiやThinkPadなどのデバイス専用の設定を提供しています。利用できるモジュールはリポジトリのflake.nixを直接見るのが手っ取り早いです。

xremapはデスクトップ環境でキーマップを変更するツールです。NixOS用が提供されています。CapsLockをCtrlに変えたり、Ctrl + HをBackSpaceにしたりできます。一部のアプリケーションにだけ適用、または除外することもできます。

flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+   nixos-hardware.url = "github:NixOS/nixos-hardware/master"; # ハードウェア設定のコレクション
+   xremap.url = "github:xremap/nix-flake"; # キー設定をいい感じに変更できるツール
  };

  outputs = inputs: {
    nixosConfigurations = {
      myNixOS = inputs.nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
+       specialArgs = {
+           inherit inputs; # `inputs = inputs;`と等しい
+       };
      };
    };
  };
}
configuration.nix
{
+ inputs,
  config,
  pkgs,
  ...
}:
{
  imports =
    [
      # ...
    ]
+   # 環境に応じてインポートするモジュールを変更してください
+   ++ (with inputs.nixos-hardware.nixosModules; [
+     common-cpu-amd
+     common-gpu-nvidia
+     common-pc-ssd
+   ]);
+   # xremapのNixOS modulesを使えるようにする
+   ++ [
+     inputs.xremap.nixosModules.default
+   ]

+ # xremapでキー設定をいい感じに変更
+ services.xremap = {
+   userName = "ユーザー名";
+   serviceMode = "system";
+   config = {
+     modmap = [
+       {
+         # CapsLockをCtrlに置換
+         name = "CapsLock is dead";
+         remap = {
+           CapsLock = "Ctrl_L";
+         };
+       }
+     ];
+     keymap = [
+       {
+         # Ctrl + HがどのアプリケーションでもBackspaceになるように変更
+         name = "Ctrl+H should be enabled on all apps as BackSpace";
+         remap = {
+           C-h = "Backspace";
+         };
+         # 一部アプリケーション(ターミナルエミュレータ)を対象から除外
+         application = {
+           not = ["Alacritty" "Kitty" "Wezterm"];
+         };
+       }
+     ];
+   };
+ };
}

その他の便利な設定

configuration.nix
{ config, pkgs, ... }:
{
+ # カーネルを変更する
+ boot.kernelPackages = pkgs.linuxKernel.packages.linux_zen;

+ # ホスト名を変更する
+ networking.hostName = "好きなホスト名";

  nix = {
    settings = {
+     auto-optimise-store = true; # Nix storeの最適化
      experimental-features = ["nix-command" "flakes"];
    };
+   # ガベージコレクションを自動実行
+   gc = {
+     automatic = true;
+     dates = "weekly";
+     options = "--delete-older-than 7d";
+   };
  };

+ # プロプライエタリなパッケージを許可する
+ nixpkgs.config.allowUnfree = true;

+ # tailscale(VPN)を有効化
+ # 非常に便利なのでおすすめ
+ services.tailscale.enable = true;
+ networking.firewall = {
+   enable = true;
+   # tailscaleの仮想NICを信頼する
+   # `<Tailscaleのホスト名>:<ポート番号>`のアクセスが可能になる
+   trustedInterfaces = ["tailscale0"];
+   allowedUDPPorts = [config.services.tailscale.port];
+ };

+ # サウンド設定 - (デフォルトでこうなっているかもしれない)
+ sound.enable = true;
+ hardware.pulseaudio.enable = false; # pipewireに置き換える
+ security.rtkit.enable = true; # pipewireに必要
+ services.pipewire = {
+   enable = true;
+   alsa.enable = true;
+   alsa.support32Bit = true;
+   jack.enable = true;
+   pulse.enable = true;
+ };

+ # マイク用ノイズ低減アプリ
+ programs = {
+   noisetorch.enable = true;
+ };

+ # Dockerをrootlessで有効化
+ virtualisation = {
+   docker = {
+     enable = true;
+     rootless = {
+       enable = true;
+       setSocketVariable = true; # $DOCKER_HOSTを設定
+     };
+   };
+ };

+ # Linuxデスクトップ向けのパッケージマネージャ
+ # アプリケーションをサンドボックス化して実行する
+ # NixOSが対応していないアプリのインストールに使う
+ services.flatpak.enable = true;
+ xdg.portal.enable = true; # flatpakに必要

+ # Steamをインストール
+ # Proton ExperimentalはSteamの設定から有効化する
+ programs.steam = {
+   enable = true;
+   remotePlay.openFirewall = true;
+   dedicatedServer.openFirewall = true;
+ };

+ # Steamのフォントが文字化けするので、フォント設定を追加
+ # SteamだけフォントをMigu 1Pにする
  fonts = {
    fonts = with pkgs; [
      # ...
+     migu
    ];
    fontDir.enable = true;
    fontconfig = {
      # ...
+     localConf = ''
+       <?xml version="1.0"?>
+       <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
+       <fontconfig>
+         <description>Change default fonts for Steam client</description>
+         <match>
+           <test name="prgname">
+             <string>steamwebhelper</string>
+           </test>
+           <test name="family" qual="any">
+             <string>sans-serif</string>
+           </test>
+           <edit mode="prepend" name="family">
+             <string>Migu 1P</string>
+           </edit>
+         </match>
+       </fontconfig>
+     '';
    };
  };
}

home-manager

NixOS modulesはどちらかというとシステム環境向けの機能であり、ユーザー環境向けの環境構築ツールが別に存在します。

hoem-managerとは、nix-community製のユーザー環境構築ツールです。こちらはNixと同様、他のディストリビューションやMacでも利用することができます。root権限が必要な領域までは設定できない一方、ユーザー環境で動作するアプリケーションに関してはNixOS modulesより多彩な設定ができます。

導入

NixOS modulesとして導入する方法とhome-manager単体で導入する方法があります。非NixOS環境でもhome-managerの設定を再利用できるように、今回はhome-manager単体で導入します。

flake.nix
{
  inputs = {
    # ...
+   home-manager = {
+     url = "github:nix-community/home-manager";
+     inputs.nixpkgs.follows = "nixpkgs";
+   };
  };

  outputs = inputs: {
    # ...
+   homeConfigurations = {
+     myHome = inputs.home-manager.lib.homeManagerConfiguration {
+       pkgs = import inputs.nixpkgs {
+         system = "x86_64-linux";
+         config.allowUnfree = true; # プロプライエタリなパッケージを許可
+       };
+       extraSpecialArgs = {
+         inherit inputs;
+       };
+       modules = [
+         ./home.nix
+       ];
+     };
+   };
}
home.nix
{
  home = rec { # recでAttribute Set内で他の値を参照できるようにする
    username="ユーザー名";
    homeDirectory = "/home/${username}"; # 文字列に値を埋め込む
    stateVersion = "22.11";
  };
  programs.home-manager.enable = true; # home-manager自身でhome-managerを有効化
}

ビルドします。inputs.nixpkgs.followオプションを付けて導入している都合上、flake.lockを削除してからビルドする必要があります。削除後のステージングを忘れないでください。

rm flake.lock
git add .
nix run nixpkgs#home-manager -- switch --flake .#myHome

第1世代のProfileが作成されたら成功です。

home.file

home-managerは、NixのProfilesを利用して、任意のファイルを$HOME以下の場所にシンボリックリンクとして配置できます。

home.file = {
  "wallpaper.png" = {
    target = "Wallpaper/wallpaper.png"; # ~/Wallpaper/wallpaper.pngに配置
    source = ./wallpaper.png; # 配置するファイル
  };
};

home.packages

インストールしたいパッケージを指定します。

home.nix
+ home.packages = with pkgs; [
+   bat
+   bottom
+   exa
+   httpie
+   pingu
+   ripgrep
+ ];

ここまで何度かwith構文が出てきましたが、上と下のコードは等しいです。

home.packages = [
  pkgs.bat
  pkgs.bottom
  pkgs.eza
  pkgs.httpie
  pkgs.pingu
  pkgs.ripgrep
];

programs

NixOS modulesと同様、programsを利用して様々な設定を行えます。
以降の設定はあくまで例ですので、Home Manager option searchで適宜検索してください。

zsh & starship

zsh.nix
{pkgs, ...}: {
  programs.zsh = {
    enable = true;
    autocd = true; # cdなしでファイルパスだけで移動
    enableCompletion = true; # 自動補完
    enableAutosuggestions = true; # 入力サジェスト
    syntaxHighlighting.enable = true; # シンタックスハイライト
    shellAliases = {
      cat = "bat";
      grep = "rg";
      ls = "eza --icons always --classify always";
      la = "eza --icons always --classify always --all ";
      ll = "eza --icons always --long --all --git ";
      tree = "eza --icons always --classify always --tree";
    };
  };
}
starship (アイコンをNerdfonts化)

starshipで以下の設定を有効化したい場合、NixOS modulesのstarshipを無効化してください。

configuration.nix
- starship = {
-   enable = true;
- };
+ # starship = {
+ #   enable = true;
+ # };
starship.nix
{
  programs.starship = {
    enable = true;
    settings = {
      # Nerd Font Symbols
      aws.symbol = "  ";
      buf.symbol = " ";
      c.symbol = " ";
      conda.symbol = " ";
      dart.symbol = " ";
      directory.read_only = " ";
      docker_context.symbol = " ";
      elixir.symbol = " ";
      elm.symbol = " ";
      git_branch.symbol = " ";
      golang.symbol = " ";
      guix_shell.symbol = " ";
      haskell.symbol = " ";
      haxe.symbol = "⌘ ";
      hg_branch.symbol = " ";
      java.symbol = " ";
      julia.symbol = " ";
      lua.symbol = " ";
      memory_usage.symbol = " ";
      meson.symbol = "喝 ";
      nim.symbol = " ";
      nix_shell.symbol = " ";
      nodejs.symbol = " ";
      os.symbols = {
        Alpine = " ";
        Amazon = " ";
        Android = " ";
        Arch = " ";
        CentOS = " ";
        Debian = " ";
        DragonFly = " ";
        Emscripten = " ";
        EndeavourOS = " ";
        Fedora = " ";
        FreeBSD = " ";
        Garuda = "﯑ ";
        Gentoo = " ";
        HardenedBSD = "ﲊ ";
        Illumos = " ";
        Linux = " ";
        Macos = " ";
        Manjaro = " ";
        Mariner = " ";
        MidnightBSD = " ";
        Mint = " ";
        NetBSD = " ";
        NixOS = " ";
        OpenBSD = " ";
        openSUSE = " ";
        OracleLinux = " ";
        Pop = " ";
        Raspbian = " ";
        Redhat = " ";
        RedHatEnterprise = " ";
        Redox = " ";
        Solus = "ﴱ ";
        SUSE = " ";
        Ubuntu = " ";
        Unknown = " ";
        Windows = " ";
      };
      package.symbol = " ";
      python.symbol = " ";
      rlang.symbol = "ﳒ ";
      ruby.symbol = " ";
      rust.symbol = " ";
      scala.symbol = " ";
      spack.symbol = "🅢 ";
    };
  };
}

Git

git.nix
{pkgs, ...}: {
  programs.git = {
    enable = true;
    userName = "Git用のユーザー名";
    userEmail = "Git用のメールアドレス";
  };

  # GitHub CLI
  programs.gh = {
    enable = true;
    extensions = with pkgs; [gh-markdown-preview]; # オススメ
    settings = {
      editor = "nvim";
    };
  };
}

Neovim

Neovimのプラグイン管理を全てNixで行うことで、環境を移行してもhome-manager switchすればどこでもNeovim環境を再現できます。
(私の場合、Vimプラグインはlazy.nvimで管理し、LSPやフォーマッタだけNixで管理しています。)

extrapackagesで指定したパッケージは、ユーザー環境にはPATHが反映されずNeovim内部でのみ有効になります。home.packagesで指定したパッケージと重複しても問題ありません。

neovim.nix
{pkgs, ...}: {
  programs.neovim = {
    enable = true;
    viAlias = true;
    vimAlias = true;
    plugins = with pkgs.vimPlugins; [
      # Treesitter
      (nvim-treesitter.withPlugins (plugins:
        with plugins; [
          tree-sitter-markdown
          tree-sitter-nix
          # ...
        ]))
      telescope-nvim
      # ...
    ];

    # Neovim内部でのみPATHが通されるパッケージ
    # LSPやフォーマッタ、その他Neovimから呼び出すツールを指定しよう
    extraPackages = with pkgs; [
      ripgrep
      biome
      nodePackages.eslint
      nodePackages.prettier
      nodePackages.typescript-language-server
      # ...
    ];

    # ~/.config/nvim/init.luaに文字列が展開される
    extraLuaConfig = builtins.readFile ./init.lua;
  };
}

builtins.<関数名>でNix言語のビルトイン関数を呼び出せます。ここではreadFile関数でファイルを文字列として読み込んでいます。

direnv

direnv.nix
{
  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };
}

言語・ツール

グローバルにツールをインストールしていきます。

development.nix
{pkgs, ...}: {
  home.packages = with pkgs; [
    gcc
    go
    nodejs-slim # npmのないNode.js単体
    nodePackages.pnpm
    nodePackages.wrangler
    deno
    bun
    python312
    zig
  ];
}

私の場合、しっかり開発環境を整えたい時は、グローバルにインストールしたツールは使わず、プロジェクトごとにflake.nixでdevShellを構築しています。

Rustツールチェーン

RustをNixで入れる場合、そのままだとツールチェーンの管理が面倒なのでoxalica/rust-overlaynix-community/fenixの利用を強く推奨します。例としてrust-overlayを追加してみましょう。

flake.nix
{
  inputs = {
    # ...
+   rust-overlay.url = "github:oxalica/rust-overlay";
  };

  outputs = inputs: {
    # ...
    homeConfigurations = {
      myHome = inputs.home-manager.lib.homeManagerConfiguration {
        pkgs = import inputs.nixpkgs {
          system = "x86_64-linux";
          config.allowUnfree = true; # プロプライエタリなパッケージを許可
+         overlays = [(import inputs.rust-overlay)];
        };
        extraSpecialArgs = {
          inherit inputs;
        };
        modules = [
          ./home.nix
        ];
      };
    };
}
development.nix
{pkgs, ...}: {
  home.packages = with pkgs; [
    # ...
+   rust-bin.stable.latest.default
  ];
}

Overlaysはnixpkgsを上書き・拡張するための仕組みです。そのため、rust-overlayで追加したパッケージは通常のパッケージと同様、pkgsを介してアクセスできます。rust-bin.stable.latest.defaultは、Rustの最新のStableのツールチェーンを一括インストールします。

また、以下のようにしてビルドターゲットを追加できます。

- rust-bin.stable.latest.default
+ (rust-bin.stable.latest.default.override {
+   targets = ["wasm32-unknown-unknown" "wasm32-wasi"];
+ })

これらは本来rustupで管理するものですが、Nixを使って宣言的に管理することもできます。

Wezterm

ターミナルエミュレータです。他にもAlacrittyやKittyなどもprogramsで設定できます。

wezterm.nix
{
  programs.wezterm = {
    enable = true;
    extraConfig = builtins.readFile ./wezterm.lua;
  };
}
wezterm.lua
local wezterm = require("wezterm")

return {
	-- Theme
	color_scheme = "MyTheme",

	-- Font
	font = wezterm.font_with_fallback({
		{ family = "JetBrainsMono Nerd Font", weight = "Regular" },
		{ family = "JetBrainsMono Nerd Font", weight = "Regular", assume_emoji_presentation = true },
		{ family = "Noto Sans CJK JP" },
	}),
	font_size = 14.0,

	-- Padding
	window_padding = {
		left = 10,
		right = 10,
		top = 10,
		bottom = 10,
	},

	-- Tab
	use_fancy_tab_bar = false,
	hide_tab_bar_if_only_one_tab = true,

	-- Misc
	use_ime = true, -- Enable IME
	check_for_updates = false, -- Disable update check
	audible_bell = "Disabled", -- Disable bell
}

ブラウザ

FirefoxやChrome, Brave, Vivaldi等の各種Chromiumブラウザをprogramsで設定できます。
commandLineArgsで起動時のオプションを追加できます[4]

browser.nix
{
  programs = {
    firefox.enable = true;
    google-chrome.enable = true;
    vivaldi = {
      enable = true;
      commandLineArgs = ["--enable-features=WebUIDarkMode" "--force-dark-mode"];
    };
  };
}

VS Code

programs.vscode.enabal = true;でVS Codeを有効化すると、settings.json等の設定ファイルがNixによって書き込み権限をロックされるため、VS Codeの設定同期機能が使えなくなります。同期機能を使いたい場合は、home.packages = [pkgs.vscode];でインストールしてください。

その他アプリケーション

apps.nix
{pkgs, ...}: {
  # Spotify TUI
  programs.ncspot.enable = true;

  # OBS
  programs.obs-studio.enable = true;

  home.packages = with pkgs; [
    discord
    discord-ptb
    gnome.totem # ビデオプレーヤー
    gnome.evince # PDFビューアー
    parsec-bin # 超速いリモートデスクトップクライアント
    remmina # VNCクライアント
    slack
    spotify
  ];
}

有効化

モジュール化した場合、importsでファイルパスを指定してインポートします。importとは動作が若干異なり、importsでは指定したファイルの出力部分がそのまま呼び出し元のコードに展開されると考えてください。

home.nix
{
  imports = [
    ./zsh.nix
    ./starship.nix
    ./neovim.nix
    ./direnv.nix
    ./development.nix
    ./wezterm.nix
    ./browser.nix
    ./apps.nix
  ];

  # ...
}

もう一度コマンドを実行するとインストールが始まり、ユーザー環境の構築が終わります。あとは自分好みに設定を変えていきましょう。お疲れさまでした。

痛み

要はバランスです

困った時はDockerを使おう

ここまで読んでくださった方ならNixOSの魅力を分かってくれたと思います。
しかし一方、NixOSはガッチガチに思想の強いLinuxディストリビューションであり、必然的にトレードオフが発生します。非純粋性の塊のようなシステムをここまで上手く扱えていることを称賛すべきなのですが、できないことも正しく知る必要があります。

イミュータブルなファイル

当然ながらファイルの可変性を要求するソフトウェアとは相性が悪いです。例えば、Nixで管理している設定ファイルを何らかのソフトウェアが書き換えようとする(e.g. .bashrc等を書き換えてPATHにディレクトリを追加するインストーラー)と、Nixが書き込み権限を制限しているため失敗します。それが困る場合は該当する設定やパッケージをNixの管理外に置けば済む話で、実際他のLinuxディストロやMacでNix単体を使うだけならほとんど問題ないのですが、システムのベースがNixのNixOSでは上手くいかない場合もあります。

Nixの籠の外

NixOSにおいて、独自のインストーラーやバイナリ形式で配布しているソフトウェアをインストールするには、ユーザーがNix式を書いてラップしてやる必要があります。
例えば実行可能ファイルを直接ダウンロードする形式の場合、nixpkgsのFetcherを利用すればダウンロードできます。ただし、再現性を担保するために事前にハッシュを指定する必要があります。もし、フェッチしてきたファイルのハッシュと指定したハッシュが異なればダウンロードが失敗します。

また、現代の開発ではプログラミング言語固有のパッケージマネージャで依存関係を管理をするのが常ですが、Nixパッケージとしてビルドするにはそれらのパッケージ情報をNix側に持ってくる必要があります。Rustなど健全なパッケージマネージャを持つ言語との相性はいいのですが、一部の行儀の悪いパッケージマネージャをラップしようとすると大変な手順を踏む場合があります。

情報がない

はい、情報がありません。NixOSはLinuxユーザー御用達のArch Wikiが通用しない数少ないディストロの1つであり、代わりにNixOS DiscourseやGitHubのissueとにらめっこすることになります。
NixOSやhome-managerの設定について知りたいなら、他人のGitHubリポジトリを見に行ってコードを読み、NixOS searchHome Manager option searchでオプションの詳細を検索しましょう。そしてあなたも記事を書いてください!

NixOS is the best

環境を破壊して起動しなくなったArch Linuxを前に悲しみに暮れていた頃、ロゴがカッコいいからとノリでインストールしてみたら、気づいたら手元のWindowsマシンが全てNixOSになっていました。感銘を受けてNixOSのパワーは素晴らしいぞと吹聴して回ったのですが、誰も使ってくれなかったのでこの記事を書きました。

How Nix manage packages
学校の共有スペース

これがBestなLinuxディストリビューションだ!と本気で言っているわけではないですが、最おもしろディストロではあると思うので、ぜひ触ってみてください。

参考

脚注
  1. NixOSのミニマルISOのビルド再現性は100%に達している。 ↩︎

  2. 当時、デスクトップ環境としてHyprlandを利用していましたが、現在はXorg + XMonadを使っています。 2024年2月にXMonadからHyprlandに再移行しました。 ↩︎

  3. pnpmみたいですね。念の為言っておくとNixの方が古いです。 ↩︎

  4. ブラウザを完全にDarkにする ↩︎

GitHubで編集を提案

Discussion

よどよど

NixOS で僕でも仕事出来るくらいの情報まとまり過ぎですね。
ただ、書かれている以上の事をするには、恐ろしく苦労しそうというのが感想です。

higelinhigelin

同じように、昔Kubuntu 5.04 (Hoary Hedgehog)に触れて手元のマシン(Debian以外)全部Kubuntuにした者(ちなみにそれ以前からのHaskeller)ですが…

やっぱりNixOSは設定の敷居が高いというか、自分の中の使い易さは
(K)ubuntu>Debian>Windows>NixOS
(Nixは使ったことあるけどNixOSはすぐ挫折)という感じです。

多分貴方ももし先にUbuntuに出会ってたら全部Ubuntuにしてたのでは?と思ったり。

Linux desktop使う人が少ないのって、単に、

  1. 日本でプレインストールのデスクトップ機が売ってない。
  2. 周りに使ってる人がいなくて、よくわからない。
    という程度のことだと思うのですが、どうでしょう?
AsahiAsahi

やっぱりNixOSは設定の敷居が高いというか(中略)

多分貴方ももし先にUbuntuに出会ってたら全部Ubuntuにしてたのでは?と思ったり。

私のOS遍歴は
Windows→Ubuntu→ArchLinux→NixOS
となっています。

確かにWindowsもUbuntuもNixOSより簡単に扱えるOSですが、それらの設定を自分の脳で管理するには複雑過ぎました。
私が触れてきた技術がReact(宣言的UIライブラリ)だったこともあり、とにかくシステムを宣言的かつ透過的に扱いたいという欲求が根底にあります。私としては、これは設定の容易性よりも重要なものです。
病的かもしれませんが、宣言的プログラミングの潮流を見る限り(そしてこの記事への反応を見る限り)、共感してくれる人は多いのではないかと考えています。

Linux desktop使う人が少ないのって、単に...

おっしゃる通りです。ただ、この記事の意図はLinuxデスクトップに関する一般論ではなく、NixOSの紹介です。NixOSが解決しうる課題として、例の3つ(煩雑さ、非透過性、非安全性)を挙げました。文章の書き方が誘導的だったことは反省しています……🙇

これがBestなLinuxディストリビューションだ!と本気で言っているわけではないですが、最おもしろディストロではあると思うので、ぜひ触ってみてください。
(NixOS is the bestの項より)

総じて、私の文章の拙さとユーモア欠乏症が原因で、大真面目な主張のようになってしまいましたが、本当は「面白いもの見つけた!みんな見てくれ〜!」という心の叫びを文章に書き起こしたものです。この点を理解していただけると嬉しいです!

ありあなありあな

素晴らしい記事をありがとうございます.おかげで一度は挫折したNixOSの環境構築を行うことが出来ました.
一つだけ,Flake化した後のビルドコマンドは

sudo nixos-rebuild switch --flake .#myNixOS

の誤りでしょうか.switchが入っていないとnixos-rebuildコマンドのmanが開いてしまうようです.

AsahiAsahi

ああ!仰るとおりです。該当箇所を修正しました。
ありがとうございます🙇

AsPulse / あすぱるAsPulse / あすぱる

素敵な記事ありがとうございます!! これで私もさいつよLinuxになれそうです!!!!!

nix run nixpkgs:home-manager -- switch --flake .#myHome

ここの部分なのですが、nixpkgs:home-managernixpkgs#home-manager の誤りでしょうか……??
記事のままですと、

error: input 'nixpkgs:home-manager' is unsupported

と出力されます

yujisyujis

configuration.nixとhardware-configuration.nixをコピーしてGitリポジトリを作ります。
の箇所で,2点質問があります.

  1. nix shell emacs としたのですが,error: cannot find flake 'flake:emacs' in the flake registriesと出てしまいました.vimでも同じだったのですが,どうすればいいですか?
  2. git init としたのですが,git : command not found と出てしました.
    すばらしい記事に出会えて手を動かしてやってるのですが,linuxが初心者なので,とても初歩的なことを聞いてしまいすいません.
AsahiAsahi
  1. 詳しくはここで説明していますが,nix shellは引数として<Flake>#<パッケージ>を受け取ります.Nixpkgsのemacsを使うならnix shell nixpkgs#emacsとします.
  2. command not foundはシェルにおいてコマンドをPATHから発見できなかったときに出るメッセージです.この場合はGitがインストールされていないようなので,nix shellで一時的にインストールするといいと思います.
yujisyujis

再度の投稿ですいません.
シェル環境の整備のセクションで
environment.systemPackagesはどこを意味していますか?.(ドット)で区切られている意味もよく理解できてません.environment.systemPackagesはどこに書けばいいのでしょうか?

また,programs.starship.enable = true とはどういう意味でしょうか?こちらもprograms.starship.enable = trueはどこに書けばいいのでしょうか?

加えて
/# 一部アプリケーション(ターミナルエミュレータ)を対象から除外
application = {
not = ["Alacritty" "Kitty" "Wezterm"];
一部アプリケーション(ターミナルエミュレータ)を対象から除外とありますが,よく理解できませんでした.
ご教授いただければ幸いです.

AsahiAsahi

NixOS modulesの設定は全てconfiguration.nixに書けば大丈夫です.

Nix言語でのドットは,一般的なプログラミング言語のドットとほぼ同じです.

environment = {
  systemPackages =}

{
  environment.systemPackages =}

は等価です.単に書き方の問題です.

xremapはキーマッピングをアプリケーションによって適用するかしないかを指定できます.ここでは人気のあるLinuxのターミナルエミュレータを適用対象から除外していますが,あくまで例です.詳しくはxremapのドキュメントを読んでください.
ドライバ・キーリマップ

yujisyujis

コメントしていただき誠にありがとうございます.だいぶ理解が進みました.ありがとうございます.

今home-manager のセクションでchromeとfirefoxを入れました.firefoxはコンソールにて,
''' firefox ''' とやれば,起動したのですが,chromeは''' google-chrome'''としてもcommand not foundになりました.GUIではインストールしたのを確認したので,PATHの問題かと思います.nixOSではPATHの環境設定をどのようにhome.nixやflak.nixに書くのでしょうか?

お手数をおかけしますが,どうぞよろしくお願いいたします.

AsahiAsahi

それは問題ではありません.Nixを含むほとんどのパッケージマネージャでは,必ずしもパッケージの名前=実行可能ファイルの名前というわけではありません.google-chromeの場合は恐らくgoogle-chrome-stableで起動できるでしょう.他にも例を挙げるとcoreutils(Linuxの代表的なコマンド群.NixOSにはプリインストール済)はlscpなど複数の実行可能ファイルを内包しています.

また,GUIからは「Google Chrome」としてインストールされているように見えるのは,GUIアプリケーションのインストール時にデスクトップエントリ(.desktopファイル)を所定の場所に配置するからです.
https://wiki.archlinux.jp/index.php/デスクトップエントリ

yujisyujis

ありがとうございます。実際に手を動かしたら、理解が深まりました。
あるflake.nixを以下のように作り、nix develop として運用していたのですが、これをpc立ち上げ時にユーザー環境になるようにしたいですが、うまくいかないです。

{
  description = "base-tool-nixOS";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; # 特定のバージョンを指定
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils, ... }: 
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          config = {
            allowUnfree = true; # ROCmなどの非フリーパッケージを利用する場合に必要
          };
        };
      in
      {
        devShell = pkgs.mkShell {
          buildInputs = [
            pkgs.gcc13
            pkgs.curl
            pkgs.python313
            pkgs.texlive.combined.scheme-full 
            pkgs.rocmPackages.rpp
            pkgs.rocmPackages.clr
            pkgs.gnumake
          ];
        };
      }
    );
}

home.nixにうめこめばいいと思ったのですが、どうにもうまくうごかないです。pkgs.mkShellからインストール項目を取ってきたいです
ユーザー設定はAsahiさんのものからほとんど変えていないです。

お手数ですが、助言をいただければ幸いです