❄️

NixOSの使い方とArch Linux、Gentooとの比較

2023/12/25に公開

みなさん、Arch Linuxを楽しんでいますか?
私もかつてはArch Linuxを楽しむ一人でしたが、現在はNixOSというディストリに切り替えています。
普通の人は聞き慣れないであろうNixOSとはどんなディストリなのか?
それと具体的な使い方についての参考記事をここに記しておきたいと思います。

まあ冒頭はこれからNixOSを始める方向けっぽい出だしでしたが
この記事に辿り着くのは既にNixOSをインストールしている人も多いでしょうから
実際の記事の内容に関してはNixOSを利用する上でのハマりポイントなどの実用的な話も大量に記述しておきたいと思います。

対象読者レベル:ArchやGentooにある程度慣れている人
(主にArchユーザーを想定して記事を書くので他の人は脳内で置き換えてください)

まずは目次をご覧ください。

Arch Linuxの利点と欠点

さて、Arch Linuxの素晴らしさは何でしょうか?
ローリング・リリース? AURの膨大なパッケージ? Arch Wikiの存在?
一言で言い表すのは難しいですが、様々な素晴らしさが含まれていることは間違いのないディストリです。

では、Arch Linuxの欠点は何でしょうか?
個人的にArchの弱点は、その高度なカスタマイズ性と豊富なドキュメントのために、自力では再現不可能なほどのカスタマイズを気軽に施せてしまうところにあると考えています。

数年間利用したArch Linuxの環境を他のPCで再現しようとした際に
自分で設定していた重要項目が抜け落ちていて動作が違ってしまったという経験をした方は多いでしょう。

そのために自分がしてきたカスタマイズをメモしておいたり
自作のスクリプトに落とし込むのがArch Linuxに慣れてきた人の定番の使い方となっています。

ブログ記事などに変更点を書き残す人もいます。
Arch Wikiのようなドキュメント文化が発展してきた要因の一つでもあり
ドキュメントがないと困ることから大量のドキュメントが作られてきました。

Arch Linuxはミニマルが良いというよりミニマルでないと良くない

Arch Linuxのもう一つの弱点は、これは実際のユーザーでないと伝わりにくいと思いますが
一度肥大化させてしまうと小さくするのがなかなか難しいという点にあります。
特にAURの利用を開始するとAUR同士での依存や、既存パッケージのAUR版への置き換えなどが入るので、相当慎重に扱わないと元に戻せなくなってしまいます。

上級者になると自作のPKGBUILDと最低限のAURパッケージ(バイナリ配布ソフトなど)に落ち着きがちになるのもそのためです。

カスタマイズ性は高いのに、元に戻すのが難しいから、冒険的なカスタムには慎重にならざるを得ないのです。

NixOSでArch Linuxの弱点が解消される

この元に戻すのが難しいというArch Linuxの弱点を解消したのがNixOSです。

Arch Linuxに例えるなら、一つの巨大な自作PKGBUILDで、インストールされる全パッケージを管理するようなイメージ。

  • パッケージは直接インストールやアンインストールは行わずに全てそのPKGBUILDの編集を経由する
  • 全パッケージのインストール処理がPKGBUILD内に直書きしてあるのでパッチ追加などの細かい調整が可能
  • PKGBUILDはGITで管理して、他のPCにはそれだけ移行すれば簡単に環境の同期が完了する
  • PKGBUILDにはインストールするパッケージだけでなく細かいアプリごとの設定も定義してある

この〇〇キマってる状態で考えたとしか思えないような理想論を体現してあるのがNixOSです。

NixOSはあなたが辿り着く最後のディストリビューション[1]

ここまでの説明で薄々感じ取ったと思いますが
NixOS、全くLinux初心者向けではありません。というか中級者ですら全然向いてない
Arch Linuxで例えるとPGKBUILDを自分で編集できないと何もインストール出来ないような、ハードめなディストリです。

ArchやGentooを弄り倒しておいて、少なくとも自分の用途で遭遇するような一通りのトラブルとその解消を経験しておいてからでないとNixOSを扱うのは相当厳しいです。

確実に、途中で挫折してArchに戻ることになります。

NixOSで出来ること

ここからはNixOSで出来ることをどんどん記述していきます。
ただしNixOSコミュニティの文化に則って書くのである程度NixOSに精通していないと意味不明になります。
だけどいいんです。今は分からなくてもNixOSに少し慣れたら再読してみてください。
NixOSはそもそもが初心者向けではないため、初心者向けの記事にするとマジで何も書けない[2]ので初心者の時期は自力で脱出して貰うという前提で省略していきます。

先に注意点

ギリギリまで実機にインストールしてはいけない

通常のディストリの感覚ですと、仮想環境である程度触って慣れてきたら、早速実機にインストールして設定を行うのが一般的ですが
NixOSの場合は全ての設定を移行先で再現可能なので
可能な設定は仮想環境で予め全て構築しておいてから実機に移行してください。

ブラウザやVSCodeの拡張機能などの細かい設定に関しても、事前に仮想環境で定義しておきましょう。
ハードウェア関連の設定以外は完璧にしてから、構築済みの.nixファイルを元にして実機に環境を再現するという方法が基本です。

NixOSの場合は実機で設定を触ることと、仮想環境で設定を触ることに意味の違いはありません。
結果的に同じ.nixファイルが出来上がるなら同じ設定です。

flakeを使ってNixOSをインストールする方法

NixOSのインストールの際に、仮想環境や他のPCで育てたflakeを扱いたい場合もあるでしょう。

# パーティション切りと/mntと/mnt/bootへのマウントは事前に済んでいるものとする
# まずはconfigの作成
nixos-generate-config --root /mnt

# ハードウェア関連の.nixだけは入れ替えるなりしておく

# flakeを使ってインストール (rootのパスワード不要の場合)
nixos-install --no-root-password --impure --flake /path/to/flake#hogehoge

rootのパスワード不要ならnixファイルで定義してないユーザーは全て消しておく

# configuration.nix
  users.mutableUsers = false;

パーティションの分け方

NixOSは気軽に様々なカーネルを試せる関係で割とEFIパーティションが圧迫されるので2GB程度は確保することをオススメします。

個人的にBtrfsのサブボリュームでの細かい分割はオススメしない

NixOSの独特すぎる挙動とBtrfsのサブボリュームの仕様を組み合わせた影響が直感的に分かりにくいので個人的にBtrfsのサブボリュームで細かく分割する方法はオススメ出来ません。
一部のHow Toでは/nixまでサブボリュームで分割していたりしますが、/と/homeくらいまでが無難ではないでしょうか?/homeですら一部のconfigファイルの実体は/nix/store/に存在したりするので本当はサブボリュームでの分割は避けたいくらいです。
そもそもNixOSでBtrfs自体どうなのかという問題もあります。NixOSのブート処理周りでBtrfs関連の修正が時々入ったりするので安定してるとは言えない気もしますし、自分がインストールした時にはNixOSのバグなのかBtrfsのバグなのか不明な謎の不具合を踏んで挙動がおかしくなった経験があります。
Arch Linuxの頃にはBtrfsしか使いたくないくらいの愛好家でしたが、上記の結果として自分はNixOSではext4を使っています。NixOSとBtrfsの組み合わせ由来の不具合に遭遇したら自分でプルリクエストを提出して安定化に貢献する覚悟がないのならext4を使う方が良いでしょう。

必要なディスク容量

お試しでunstableに切り替えたりしていると一瞬で/nixが100GBとかに到達します。
メインとして常用する場合は/nixだけで300GB程度は想定したほうが良いでしょう。
特にunstableを使う場合、日本語環境関連で発生時期の分からない不具合に後から気付く場合があるので世代はなるべく消さずに残しておきたいので。

USBメモリにインストールした場合にハマりがちなポイント

普通のディストリの感覚でNixOSを仮想環境からUSBメモリにインストールして、USBブートして使おうとするとハマります。
これはハードウェアの定義が仮想環境のUSBメモリと物理USBメモリで違うというのが理由です。
特に最近のUSBメモリはスティック型のSSDだったりするのでハマるんですね。
一番オススメなのはUSBメモリにインストールメディアを焼いたうえで、インストールメディアのブート時にメモリ上にシステムを読み込む設定を選択して起動しておくという方法です。
これだとインストールメディアの入ったUSBメモリをあとから引き抜いても、起動したインストール用のシステムが壊れないので、同じUSBメモリに対して上書きインストールすることが可能になります。
ハードウェア関連の設定も正しく反映されます。

実際のpathの探り方

whereとrealpathを組み合わせます

❯ where bash                
/run/current-system/sw/bin/bash

❯ realpath /run/current-system/sw/bin/bash    
/nix/store/xxxxxxxxxxxx/bin/bash

unstableやstagingへの切り替え

全パッケージをunstableやstagingに切り替えたい場合の方法

#flake.nix
inputs = {
    # このURLを変更することでパッケージを変更できる
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-xx.yy";

主に設定可能なURLの一覧を貼っておきます。

"github:NixOS/nixpkgs/master"; # そのままだが一部更新はstagingの方が早い
"github:NixOS/nixpkgs/staging-next"; # masterから6時間ごとに更新される、stagingでテスト完了してから更にテストが必要な場合はここに入る場合がある
"github:NixOS/nixpkgs/staging"; # staging-nextから6時間ごとに更新され、大量の再構築が必要な更新はここに先に入る、マジで有効化してはいけない
"github:NixOS/nixpkgs/nixos-unstable"; # パッケージがバイナリ化されているので安心して有効化できるが、セキュリティパッチの反映などはstableの方が早かったりする
"github:NixOS/nixpkgs/nixos-unstable-small"; # unstableより更新が早い代わりにDE関連パッケージなどでビルドが発生する。主にサーバー向け
"github:NixOS/nixpkgs/nixos-xx.yy" # いわゆる安定版、心の底からこれをオススメしたい

なお、名前の意味は深く考えてはいけません。
特定のコミットIDで指定することも可能です。

パッケージの更新

nix flake update /xxx/yyy

/xxx/yyyは/xxx/yyy/flake.nixのディレクトリ。
flake.lockが更新されるので、nixos-rebuildする。

unstableはオススメ出来ない

NixOSのコミュニティではnixos-unstableで常用している人を良く見かけますし、割と安定しているような報告もよくありますが
日本語環境では、99.99%のパッケージが安定していても、日本語入力関連のパッケージに不具合が出たら詰むので素直に安定版を使いましょう。
「nixos-unstableを常用している日本人」は恐らく数名なので、nixos-unstableで日本語関連の不具合が出た場合、あなた自身がプルリクエストを提出しない限りは誰も直しません

ユーザーパスワードの定義とハッシュ化

NixOSの場合、事前にユーザーのパスワードまで定義可能。
ハッシュ化した状態でも定義できるので比較的安全に書き込め、パスワード強度さえ妥当なら公開してもパスワードの解析は計算量的に困難です。

パスワードのハッシュの作成

# -m sha-512はなくてもハッシュの強度が変わるだけで動作はする
mkpasswd -m sha-512
Password: 
$6$76zm45jDHKL5Caju$FoYCBuLMP/paWmFXjwrLRdeCGrx6RGzDDYmoQweP2QsfL5xqMYnZSYmQPp.uTGHxRznD7tMUKx8hzv5VON50d/

作成したパスワードのハッシュを定義の方にコピペする

# configuration.nix
  users.users.user = {
    hashedPassword = "$6$76zm45jDHKL5Caju$FoYCBuLMP/paWmFXjwrLRdeCGrx6RGzDDYmoQweP2QsfL5xqMYnZSYmQPp.uTGHxRznD7tMUKx8hzv5VON50d/";
  };

パスワードを定義したので
パスワードをコマンドからは変更不可能にしておく

# configuration.nix
  users.mutableUsers = false;

初期パスワードの定義

ユーザーの作成時のみ有効になるパスワードを定義することも可能。
作成後に自分で変更したい場合などにはここに定義しよう。

# configuration.nix
  users.users.user = {
    initialHashedPassword = "$6$76zm45jDHKL5Caju$FoYCBuLMP/paWmFXjwrLRdeCGrx6RGzDDYmoQweP2QsfL5xqMYnZSYmQPp.uTGHxRznD7tMUKx8hzv5VON50d/";
  };

mutableUsers = falseだと.nix経由以外で変更できないので注意。

パスワード変更の際の注意

/etc/pam.d/passwdなどにパスワード変更時の処理を書いていても、Nix式経由でパスワードを変更すると反映されないので手動でパスワード変更に対応させましょう。

Zramの設定

# configuration.nix
  zramSwap = {
    enable = true;
    memoryPercent = 200;
  };

Fedoraのように自動的にZramを設定してくれるとかはないので自分で設定する。
一応300%くらいまではパフォーマンス良好らしいが、200%あれば十分だろう。

GNOMEとGDMの設定、dconfの編集

NixOSでオススメのDEはズバリGnomeです。
Gnomeというとdconfによる設定が分かりにくいので通常はカスタマイズを最小限に使わざるを得ないDEというイメージですが、NixOSの場合はdconfとNix式の相性が非常に良いのでカスタマイズし放題になります。

dconfの探り方

Gnomeの設定をNix式に落とし込む前に、dconfを探るコマンドを伝授しておきます。

dconf watch /

上記のコマンドを起動しながらGnomeの設定を編集すると、編集後のdconfが出力されます。

dconf設定の一例(Home Manager利用)

# home.nix
  dconf.settings = {
    "org/gnome/shell" = {
      # お気に入りのアプリ
      favorite-apps = [
        "chromium-browser.desktop"
        "code.desktop"
        "org.gnome.Nautilus.desktop"
        "gnome-system-monitor.desktop"
        "org.gnome.Console.desktop"
        "org.gnome.TextEditor.desktop"
      ];
      # 拡張機能の有効化 (導入ではなく最初から有効状態で開始するための設定)
      # 拡張機能自体のインストール方法は通常のパッケージのインストール方法と同じなので割愛
      enabled-extensions = [
        "bluetooth-quick-connect@bjarosze.gmail.com"
        "hibernate-status@dromi"
        "kimpanel@kde.org"
        "window-list@gnome-shell-extensions.gcampax.github.com"
        "aztaskbar@aztaskbar.gitlab.com"
        "drive-menu@gnome-shell-extensions.gcampax.github.com"
        "appindicatorsupport@rgcjonas.gmail.com"
      ];
    };
    "org/gnome/desktop/interface" = {
      # アニメーションの無効化
      enable-animations = false;
      # 時計を曜日まで表示
      clock-show-weekday = true;
      # 文字の大きさを変更
      text-scaling-factor = 1.25;
    };
    # 最大化ボタンや最小化ボタンを表示
    "org/gnome/desktop/wm/preferences" = {
      button-layout = ":minimize,maximize,close";
    };
    #夜間モード関係
    "org/gnome/settings-daemon/plugins/color" = {
      night-light-schedule-automatic = false; # 夜間モードの自動時刻設定を無効化 (手動で設定する)
      night-light-schedule-from = 0.0; # 夜間モードの開始時刻
      night-light-schedule-to = 0.0; # 夜間モードの終了時刻
      night-light-temperature =  lib.hm.gvariant.mkUint32 2518; # 夜間モードの色温度、dconf watch /で表示される実際のデータは「uint32 2518」
    };

    # キーボードショートカットも設定可能
    "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0" = {
      # 'code /path/to/xxx'とすれば好きなディレクトリを開ける
      # 環境によってはcodeのpathが必要になる可能性があるのでwhereなどで探る
      command = "/run/current-system/sw/bin/sh -c  'code'";
      binding = "<Super>c";
    };
  };

GDM関連の設定は方法が違う

GDMの設定もdconfを利用しますが、少し設定方法が違うので気をつけましょう。以下一例。

# configuration.nix
  # GDMのユーザーリストを非表示にする
  programs.dconf.profiles.gdm.databases = [{
    settings."org/gnome/login-screen" = {
      disable-user-list = true;
    };
  }];

自動起動するアプリの設定

アプリを自動起動させるためには自分で.config/autostartに.desktopファイルを置く方法と、自分でsystemdのユニットファイルを書く方法があります
普通のディストリの感覚でgnome-tweaksで設定しても機能しないので注意。

.config/autostartに.desktopファイルを置く

GithubのIssueで提供されていた方法。
本来のGnomeのautostartの使い方なので変なバグには遭遇しにくいはず。

# home.nix
  home.file = builtins.listToAttrs (map
    (pkg:
      {
        name = ".config/autostart/" + pkg.pname + ".desktop";
        value =
          if pkg ? desktopItem then {
            # Application has a desktopItem entry. 
            # Assume that it was made with makeDesktopEntry, which exposes a
            # text attribute with the contents of the .desktop file
            text = pkg.desktopItem.text;
          } else {
            # Application does *not* have a desktopItem entry. Try to find a
            # matching .desktop name in /share/apaplications
            source = (pkg + "/share/applications/" + pkg.pname + ".desktop");
          };
      })
    [
      # ここに自動起動したいパッケージを一覧化する
      pkgs.discord
    ]);

自分でsystemdのユニットファイルを書く

上記の方法では細かい起動フラグの設定や起動タイミングの制御が出来ません。
そこでsystemdのユニットファイルを定義するという方法もあります。

# home.nix
  # ログイン時に起動するアプリ
  systemd.user.services = {
      "discordStart" = {
        Unit = {
	  # この辺りはアプリによって柔軟に変更してよい
          After = [ "basic.target" ];
          PartOf = [ "graphical-session.target" ];
        };
        Service = {
            # journalctlでgrepする際に使う
            SyslogIdentifier = "discordStart";
            # ExecStartPost="${pkgs.coreutils}/bin/sleep 10"; # 起動のディレイの設定
            # 特定のコマンドを使いたい場合には${pkgs.xxxx}から書かないと動かない
            # ${pkgs.bash}/bin/bash -c 'xxx yyy'と渡すことで細かいコマンドも定義できなくはない
            ExecStart = "${pkgs.discord}/bin/discord  --start-minimized";
            Restart = "on-failure"; # 起動タイミングが早すぎて失敗する場合の対策
        };
        Install = {
         # systemctl --user list-units --type target等を眺めなから適切なtargetを決める
         WantedBy = [ "graphical-session.target" ];
        };
      };
    };

ExecStartに色々と詰め込んで1つのサービスで全てのautostartを定義したくなるかもしれませんが
書くのが楽以外のメリットは何もないので、素直に起動するアプリの数だけユニットファイルを書きましょう。

インストール用のisoを自作する

実はNixOSはインストール用のisoを自作可能です。
例えば最初からgitが動いたり、仮想環境で正常に動作したり、Zramが有効化されていてメモリ不足の対策がされているようなisoも簡単に作成できます。
isoの作成はNixOSではなくNixの機能なので、他のディストリ上でもisoの自作は可能です。
wslを使えばWindows上からでも作成可能。
方法自体は簡単かつ文章化されているので興味がある人は各自調べておいてください。

Swapfileの定義

  swapDevices = [{
    device = "/swapfile";
    size = 16 * 1024;
  }];

Swapfileはわざわざ手作業で作成しなくても定義するだけで自動的に作成されます。
Btrfsでも面倒な手間なくスワップファイルが作成されて有効化されます。

zshのシンタックスハイライトを有効化

とりあえずシンタックスハイライトだけ欲しい場合には1行のサクサク追記で実現可能。

# configuration.nix
  programs = {
    zsh = {
      enable = true;
      syntaxHighlighting.enable = true; # シンタックスハイライト
    };

oh-my-zshとPowerlevel10kの併用

この2つの併用は結構大変、普通に定義するとzshrcの編集が競合するので動かない。

まずp10kを直接記述してインストールする

# home.nix
  home.packages = with pkgs; [
    # oh-my-zsh以外でも同様に動くようにp10kはここでインストールしておく
    zsh-powerlevel10k
  ];

zshのinitExtraオプションに追記

# home.nix
  programs = {
    zsh = {
      initExtra = ''
      # p10kは読み込み専用のzshrcを編集できないので手動で追記する
      source $HOME/.p10k.zsh
      source ${pkgs.zsh-powerlevel10k}/share/zsh-powerlevel10k/powerlevel10k.zsh-theme
      '';

p10kの初期設定が完了すると、zshrcが読み込み専用という警告が出るので
(n) No. I know which changes to apply and will do it myself.を選択する。
(要は上記のinitExtraの設定を手動で書き込んでくれという警告)
initExtraのsource $HOME/.p10k.zsh部分は環境によって変わる場合があるので適宜編集すること。

gitの設定

git関連の設定もインストール後にいちいち弄るのが面倒なので.nixで定義しておく

# configuration.nix
  programs = {
    git = {
      enable = true;
      config = {
        credential = {
          helper = "xxxxxx";

mpvにスクリプトを追加する

mvpにサムネイルやシークバーを追加したい場合も.nix式で簡単に定義可能

# home.nix
  programs = {
    mpv = {
      enable = true;
      scripts = with pkgs.mpvScripts; [
        uosc # UIの全体的な改善
        thumbfast # サムネイルの表示
        mpv-playlistmanager # Shift + Enterでプレイリストを表示
      ];
      scriptOpts = {
        # thumbfast.network = "yes"; # YouTubeのサムネイルを表示する設定だが実用性はイマイチ
        # playlistmanager.key_showplaylist = "F8"; # プレイリスト表示のショートカットキーを変更したい場合
      };
      config = {
        hwdec = "auto"; # HW対応、デフォルト値は"no"
        loop-playlist="inf"; # 再生終了後に最初から再生し直す(プレイリスト単位)

        # mpvでYouTubeの視聴をしたい場合はyoutube-dlが必要だが
	# 代わりにyt-dlpを使いたい場合にはこのように定義する(yt-dlpは別項目でインストールしておくこと)
        script-opts="ytdl_hook-ytdl_path=${pkgs.yt-dlp}/bin/yt-dlp";
      };
    };

Chromiumと拡張機能を定義する

Chromiumと拡張機能は同時に定義可能。
Chromiumの派生ブラウザであれば同じ設定を使い回せるが、Chromeは現時点では拡張機能を定義できない。

# home.nix
    # Chromium関連の全ての設定はここに記述しておく
    chromium = {
      enable = true;
      # package = pkgs.google-chrome; # 中身をgoogle-chromeなどに変更できるが、現時点ではgoogle-chromeでは仕様の違いにより拡張機能の設定が反映されないので注意
      extensions = [
        # 拡張機能はIDで指定する、判別不能なのでコメントを追記しておくこと
        # ストアに存在しないアプリもURL指定でインストールできる
        { id = "hkgfoiooedgoejojocmhlaklaeopbecg"; } # picture-in-picture
      ];
    };

VSCodeの拡張機能の定義

VSCodeの拡張機能は一部しかパッケージ化されていないので
それ以外の拡張機能を定義するためにはひと工夫が必要になる。
方法は何通りかある

直接定義する方法。

# home.nix
    vscode = {
      enable = true;
      extensions = with pkgs.vscode-extensions; [
        # 公式にパッケージ化されている拡張機能
        eamodio.gitlens # gitLens
        jnoortheen.nix-ide # Nix用の拡張機能だが積極的にメンテされていない
	
	# 以下一例として公式パッケージ化されていないcomment-anchorsを追加する
      ] ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
        {
	  name = "comment-anchors";
	  publisher = "exodiusstudios";
	  version = "1.10.3";
	  sha256 = "sha256-IyiiS4jpcghwKI0j8s69uGNZlKnZ0o78ZCT0oZeJER0=";
        }
      ];
    };

上記の手法だとvscode関連のコード以外を触ることなく拡張機能の追加が可能なので混乱しにくいといえば混乱しにくいが、バージョンやハッシュまで指定する必要があるため、更新頻度の高い拡張機能を導入する場合には保守が面倒になる。

nix-vscode-extensionsを利用する方法

NixOSのコミュニティより、VSCodeの全ての拡張機能を毎日ビルドしてくれているリポジトリが用意されている。
このリポジトリを活用するとVSCodeの拡張機能の管理が便利になる。
ただし設定が割と複雑なので注意。

# flake.nix
  inputs = {
   # nix-vscode-extensionsのurlを追加する
   nix-vscode-extensions.url = "github:nix-community/nix-vscode-extensions";
# flake.nix
   # ここにもnix-vscode-extensionsを追記
   outputs = inputs@{ self, nixpkgs, home-manager, nix-vscode-extensions }:
# flake.nix
            home-manager.nixosModules.home-manager
            {
	      # この追記を地味に忘れてしまうと動かないので注意
              home-manager.extraSpecialArgs = { inherit inputs; };
# home.nix
# inputsを追加しておく
{ inputs, config, pkgs, lib, ... }:
# home.nix
    vscode = {
      enable = true;
      # この部分をnix-vscode-extensions....に入れ替える
      extensions = with inputs.nix-vscode-extensions.extensions."x86_64-linux".vscode-marketplace; [
        jnoortheen.nix-ide # Nix用の拡張機能だが積極的にメンテされていない
        eamodio.gitlens # gitLens
	
	# 公式ではパッケージ化されていない拡張可能も公式パッケージと同じ方法で定義可能になる
        exodiusstudios.comment-anchors # Comment Anhors
      ] ;

分かりにくいかもしれないが、分かりにくいと分からないは違うので頑張って理解しよう。

プルリクエストをチェリーピックする

例えばhome-managerならこんな感じ

# flake.nix
 outputs = inputs@{ .......... }: 
    let
      home-manager =
        let
          src = nixpkgs.legacyPackages."x86_64-linux".applyPatches {
            name = "home-manager";
            src = inputs.home-manager;
            patches = nixpkgs.legacyPackages."x86_64-linux".fetchpatch {
              url = "https://patch-diff.githubusercontent.com/raw/nix-community/home-manager/pull/xxxx.patch";
              sha256 = "sha256-xxxxxxx=";
            };
          };
        in
        nixpkgs.lib.fix (self: (import "${src}/flake.nix").outputs { inherit self nixpkgs; });
    in
    {

チェリーピックする場合は、urlを直前のコミットIDで固定するという小技もある。

# flake.nix
home-manager.url = "github:nix-community/home-manager/xxxxxxxxxxxxxxxxxxxxxx";

こうすればいちいちパッケージを更新しても競合しない。

ハイバネートの設定

事前にprotectKernelImageを無効化する必要がある。

# configuration.nix
security.protectKernelImage = false;

あとはArch Linuxのハイバネート設定と同じなので該当箇所をNix式で変更していけばいい。

Hyprlandの設定

with pkgsを使いながら書くと楽になる。
個人的にはGDM関連の設定もついでに出来るのでGnomeと併用するのがいいのかなという気がしている。
不具合に遭遇した際にHyprland特有なのかを確認するのにも便利。

# home.nix
  wayland.windowManager.hyprland = {
    enable = true;
    settings = with pkgs; {
      # ログイン時に起動するアプリ
      exec-once = [
        "${waybar}/bin/waybar"

fcitx5の起動の定義には癖があるので注意

where fcitx5で出力されるパスで書かないと挙動が安定しない。

# home.nix
exec-once = [
  "/run/current-system/sw/bin/fcitx5 -D"

swaylockを使う場合の注意点

理屈は不明だが、下記のコードがないと正常に動作しない。
執筆時点での挙動なのでもしかしたら修正パッチなどが入って必要ない可能性はある。

# configuration.nix
  security.pam.services.swaylock = { }; # 理屈ではなくswaylockを使うなら必要

キーリングの設定

# configuration.nix
  services.gnome.gnome-keyring.enable = true;
  security.pam.services.gdm.enableGnomeKeyring = true;

仮想マシンマネージャーのインストール

  virtualisation.libvirtd.enable = true;
  programs.virt-manager.enable = true;

systemdサービスとテンプレートの作成

# configuration.nix
  # テンプレートの作成
  systemd.services = {
    "test@" = {
      # ここを無効にするとテンプレート経由のサービスが全て無効になる
      enable = true;
      # nixのコマンドを使うのに必要
      path = [ pkgs.nix ];
      # 以降は通常のsystemdユニットファイルに近い記法だが、一部の項目は挙動が違うので注意
      #[Unit]
      description = "Test services.";
      partOf = [ "user@%i.service" ];
      #[Service]
      serviceConfig = {
        Type = "simple";
        Slice = "user-%i.slice";
        RemainAfterExit = "yes";
        SyslogIdentifier = "Test";
	# 特定のコマンドを使いたい場合には#{pkgs.xxxx}から書かないと動かない
        ExecStart = "${pkgs.coreutils}/bin/true";
        ExecStop = "${pkgs.bash}/bin/bash -c '${pkgs.sudo}/bin/sudo -u $(id -nu %i) ${pkgs.hello}/bin/hello'";
      };
      #[Install]
      # 残念ながらここでのwaytedByはWantedByを単純に追記している訳ではないのでテンプレートでは機能しない (執筆時点)
      #wantedBy=["user@%i.service"];
    };

    # 上記をユーザー1000で有効化、その基本的には手書きで有効化する
    "test@1000" = {
      # テンプレートを使う場合は必須
      # https://github.com/NixOS/nixpkgs/pull/198336
      overrideStrategy = "asDropin";
      # wantedByはテンプレートから渡されない (執筆時点)
      wantedBy = [ "user@1000.service" ];
    };

普通に関数として作ったほうが良さそうな気はする。

ntfsの利用

# configuration.nix
  boot.supportedFilesystems = [ "ntfs" ];

デュアルブート用に時刻をWindowsの仕様に合わせる

# configuration.nix
  time.hardwareClockInLocalTime = true;

systemd-hooksに変更

Arch Linuxで言うところのsystemd-hooksへの変更は1行で済む。

# configuration.nix
  boot.initrd.systemd.enable = true;

パッケージの上書き

# overlays.nix
{ config, pkgs, lib, ... }:
{
  nixpkgs.overlays = [
    (final: prev: {
      hello = prev.hello.overrideAttrs (old: {
        # 変更したい項目を書いていく
        version = "xxxxxxxx";

特定のパッケージだけclangでビルドしたり最適化したり

# overlays.nix
{ config, pkgs, lib, ... }:
{
  nixpkgs.overlays = [
    (self: super: {
      # 特定パッケージだけclangでビルドする方法
      hello = super.hello.override { stdenv = pkgs.clangStdenv;};
      # 特定パッケージだけ手元で最適化する方法
      libgcrypt = super.libgcrypt.override { stdenv = super.withCFlags [ "-O0"] super.stdenv;}; 

udevルールの記述

NixOSなら追加のudevルールも.nix内に記述可能です。

# configuration.nix
  services.udev = {
    extraRules = ''
      # ここに追加のudevルールを書いておく
    '';
  };

特定のURLからフォントを追加する

日本語環境でNixOSを使い場合に困りがちなのが、パッケージ化されていないフォントの追加方法。
手動で追加しても良いのですが、実はダウンロードURLさえ存在すれば.nixファイル内に簡単に定義できます。

# configuration.nix
  fonts = {
    packages = with pkgs; [
      noto-fonts-cjk-serif
      noto-fonts-cjk-sans
      noto-fonts-emoji
      source-han-serif # 源ノ角明朝
      twemoji-color-font
      # nerdfonts # ビルドが発生する場合があるのでオススメしない

      # 以下パッケージ化されていないフォント
      # HackGen NF
      "${pkgs.fetchzip {
        url = "https://github.com/yuru7/HackGen/releases/download/v2.9.0/HackGen_NF_v2.9.0.zip";
        hash = "sha256-Lh4WQJjeP4JuR8jSXpRNSrjRsNPmNXSx5AItNYMJL2A=";
      }}"
      # 解凍したzipが単独フォルダではない場合
      "${pkgs.fetchzip {
        url = "https://xxxxx.zip";
        hash = "sha256-xxxxxxxxxxxxxxxxxxxxxxx=";
        stripRoot = false; # 解凍したzipが単独フォルダではない場合にはこれが必要
      }}/xxxx/yyyyy" # 実際のフォントファイルがあるディレクトリのpathを記述する

バイナリを動かす

バイナリ配布されている小さなプログラム等を1回だけ動かしたい場合、いちいち.nixに落とし込むのは面倒です。
その場合はsteam-runを利用します。

steam-run ./xxxxx

これで大半のバイナリは実行可能。
最終手段として頭に入れておきましょう。

カーネルの変更とオススメのカーネル

xanmodは裏でビルドぶん回してCPU使用率100%でも表の動作はサクサクなので個人的にはオススメです。
ただし一部のニッチな使い方で不具合が出る可能性もあるので、基本的には安定版カーネルを使いましょう。

# configuration.nix
  boot.kernelPackages =  pkgs.linuxPackages_xanmod_latest; 

Gentoo化

NixOSは全パッケージに最適化のビルドフラグを渡すことが可能です。
いわゆるGentoo化が可能……ですが「Minecraftで電卓を再現可能です」くらいのニュアンスなので茨の道です。

筆者も挑戦しましたが、最近仕様の変更があったらしく、過去に通用した方法が塞がったとのことで断念しました。
(全パッケージの再構築が完了してから最後にエラーが出て失敗するので試行錯誤がキツ過ぎて心が折れた)

一応失敗した方法を記しておくので興味がある方は試行錯誤してみてください。

# overlay.nix
{ config, pkgs, lib, ... }:
{
  nixpkgs.overlays = [
    (self: super: {
      # ps -efw | grep "\-O3"で確認したところ、最適化自体はされているようだ
      stdenv = super.impureUseNativeOptimizations (super.withCFlags ["-O3"] super.stdenv);

      # 昔はこの方法で全パッケージ最適化可能だったらしい
      # stdenv = super.stdenvAdapters.addAttrsToDerivation {
      #   NIX_CFLAGS_COMPILE = "-O3 -march=native -flto";
      #   # NIX_LDFLAGS = "";
      # } super.stdenv;

      # 特定パッケージだけclangでビルドする方法 (バグでビルドがコケたパッケージに適用する)
      hoge = super.hoge.override { stdenv = pkgs.clangStdenv;}; 
      # 特定パッケージだけ-O0でビルドする方法、応用すれば特定パッケージだけ最適化も可能
      libgcrypt = super.libgcrypt.override { stdenv = super.withCFlags [ "-O0"] super.stdenv;}; 
 
    })
  ];
  # この方法も示されていたが、これも最後にエラーが出てムリ
  # nixpkgs.config.replaceStdenv = {pkgs}: pkgs.withCFlags ["-O3" "-march=native"] pkgs.stdenv;
}

NixOSをGentoo化して常用しているような人は世界にも少ないようで情報はないです
ビルドエラーの理由も、そのパッケージ内部のnix式のコード部分でのgccに渡すフラグのバグや特殊仕様が原因だったりで検索での特定が困難なので覚悟すること。

調べたコマンドをコピペするような解決策は存在せず
NixOSの内部コードを探りながら、どういったオプションを渡せば意図通りに動くかを推測しながら運用していくことになります。

Arch LinuxからNixOSに移行した際の注意点

NixOSを扱いたがるような人は基本的にArchやGentoo上がりでしょうから
Arch Linuxから移行した場合という観点から主な文化の違いによる注意点をまとめておきます。

NixOSコミュニティの特徴

Arch Linuxのコミュニティでは、回答はコピペ可能な設定ファイルやコマンドなどで答えるのが一般的ですが
NixOSコミュニティでは、コピペで動作しない半端なコードで回答したり、そもそもコードを提示せずにヒントだけ与えるような答え方が一般的です。

というのも、Nix言語がインポート等の関係で単一ファイルでは意味がない記述となってることが多いので、そもそもコピペ可能なコードを提示するというのが困難という事情があります。
なのでヒントだけ与えて、あとは各自のコードに合わせて編集して使ってね。という回答なんですね。

これは慣れてくれば分かるのですが、Arch感覚でコピペ解決を望んで調べていると、永久に回答に辿り着かないので注意してください。

検索しても出ないことは普通

Arch Linuxを使っていると、工夫して検索をすれば大半の疑問や不具合に対する回答がインターネット上に落ちているのが当たり前となりますが、NixOSでは逆に検索しても一切解決策が出てこないのが当たり前となります。

更に言えば、他のディストリなら例えマイナーであってもArch Wikiの知識を応用すれば解決可能だったりしますが、NixOSは全ての設定をNix言語経由で行うという関係上、Arch Wikiの知識が一切役に立たないような不具合や設定方針も多々あります。
つまり本当にインターネット上に文章情報がないんです。

それでも調べる場合の検索のコツ

残念なことにNixOSでは通常の検索エンジンの情報はそこまで役に立たないことが多いです。
調べたいことがあった場合には、Githubでコードを検索しておきましょう。
文章を探すのではなく、他人のリポジトリにあるnixファイルのコードを読解して自分に当てはめるというのが基本的な検索方法になります。

Redditは不具合解決では役に立たない

Arch LinuxですとRedditに回答があったりしますが、NixOSのRedditは不具合への回答が投稿されている可能性は低いです。
その割には検索結果の上位を独占しているため、NixOSの不具合の解決を検索エンジンで探りにくい要因の一つとなっています。
nixpkgsのGithub Issue > 公式コミュニティ >>> Reddit
このくらいの優先度で情報を探りましょう。

不具合の解決策がnixpkgsのコードを修正する以外にない場合もあるので
そういう意味でもGithubが一番いいです。Archなどディストリの不具合とは事情が違います。

NixOSのドキュメントが貧弱なのは何故?

これはまさに冒頭で語ったArch Linuxの弱点が解消されてしまっているが故の悲劇ですね。
NixOSの場合は、一度設定を完了してしまうと、永久にその設定を振り返る必要がないので、Wiki等を編集してドキュメントを個人的に残す動機が薄いんです。

コードにコメントでも書いておけば自分向けには足りてしまいますから
純粋に他人に情報提供したいというモチベーションでもない限りはインターネット上に文章を残す必要性もない訳です。

その代わりにnixコード全体を公開して「あとはコードから読み取ってね」という文化です。
なので検索エンジンではなくGithubで検索しないと情報が出てこないという流れ。

もっと簡単な方法があるはずという思い込みを捨てよう

NixOS関連の情報を探っていると、単純な設定の変更のために複雑怪奇な式が提示されているという状況に遭遇することが多々あります。
Arch Linuxなどの癖で「いやもっと簡単な方法があるだろ」とスルーしてしまいそうになりますが
NixOSの場合は、単純な変更のための唯一の手段が複雑怪奇な式の記述であることはよくあるので、素直に受け入れることも大切。
その変更を単純なオプションに落とし込みたいならあなたがプルリクエストを提出して機能を追加するしかありません

オプション名や機能名の英単語から安易に役割を推測しないこと

NixOSは欧州発のディストリなのもあり、貢献している人たちの英語ネイティブ比率はそこまで高くありません。
この英単語だからこの機能だろう、みたいな安易な推測は痛い目を見るので、ちゃんと実際の役割を調べる癖を付けましょう。

脚注
  1. Ubuntuという説もある。 ↩︎

  2. 日本語の間違いではありません。誰もが最初はNixOSの初心者なのに、NixOSの初心者に出来ることは何もないんです。意味不明だとしても文面そのままの意味です。 ↩︎

Discussion