ArchLinuxからNixOSに移行してみる

## 概要
ここでは、一般的なデスクトップ環境の構築、dotfilesをnixで管理する方法をまとめようと思います。
ArchLinuxやUbuntuほどメジャーでないため日本語記事が少なく、海外の記事を呼んでも微妙にわからないことが多くて辛かった。
NixOSを使ってる人は多分真っ先にコードを読んで理解できる人しか使ってないんじゃないかと思ったくらい。

## モチベーション
日常使いのOSとしてArchLinuxを使っていて特に不自由なく今日まで来ました。
各マシンで微妙に環境に差異があることからは目を背けていましたが、最近マシンを増やすことになり環境構築について再度見直す機運が高まりました。
更にこの前のアプデで`lightdm-webkit2-greeter`が起動しないということにも見舞われたのでこれを気に、1.どのマシンでも同じ環境で 2.たとえパッケージが壊れたとしても即座に復旧でき（少なくとも解決を後回しにでき） 3.あわよくばリモートマシンの管理もしたい という欲求を満たす存在として以前から気になっていたNixOSに移行しようと思いました。

## nixを用いるメリット・デメリット
- メリット
    - .dotfilesだけでなくシステム設定も宣言的に書ける
        → パッケージ周りの設定を共通にすれば作業環境に差異が生まれない
    - システムもしくはホーム環境のロールバックが可能（NixOSはシステム全体、Home Managerはユーザ環境のロールバック）
        → 万が一壊れても動作していたところに一度戻って作業ができる
    - リモートマシンへのビルドやリモートリポジトリの設定からインストールやアップデートができる
        → 作業マシンを決めておけば、git pull忘れなどで差異が生まれるということもない
    - いつどこでビルドしても同じ結果が得られる
        → 全てのビルドは入力が同じであれば出力のハッシュが同じになるようになっており、アップデート時期でパッケージが変わることがない
    - クロスプラットフォーム、他のディストリ、macOSで使用できる→いつどこでも同じ環境が作れる
        → 一度configを書いてしまえば、全てのマシンで同じ作業環境を得られる
    - 慣れれば、システム・ユーザー環境の管理だけでなく、ビルドや環境構築、コンテナの管理を全てnixで完結できる
        → むしろ、こちらが本命。
    
- デメリット
    - 敷居が高い
        → nix周りの学習だけでなく各パッケージのconfの書き方、エラーへの対処能力が問われる
    - nixのビルドが通ってもシステムが正常に起動するとは限らない
        → あくまでnixのビルドが通っただけで、ソフトのビルドが正しいかはわからない。
    - デバッグが複雑
        → 設定したはずのconfigが反映されていないように見えたり、特定のソフトが動かない原因が任意依存がモジュール化されている場合など
    - nixでできることは大体何らかのメジャーな手段がある
        - 例えば環境構築という点ではpodmanやdockerというよりメジャーな手段がある
    - 資料が少ない
        → 日本語は言わずもがな英語も（Archと比べると圧倒的に）少ない
        →  コミュニティっも比較的小さい
        → [Nixを触り始めて参考になったサイトまとめ](https://qiita.com/Sumi-Sumi/items/97b6f2c70869802ff942)を参照すればマシになるかもしれない...
    - チャンネルに無いパッケージが多い
        → 自前で作ることが求められる
    - 若干日本語環境に不備がある場合がある
        → IMEやフォント化け（大抵入れるであろうNoto Font CJKを使うとDiscordで化ける）が発生する場合がある
- 余談
    - 同一環境という点では.dotfilesをgitで管理することである程度は対応できる（特にユーザー環境）
    - ArchLinuxが優秀すぎる
        - ほぼ素のLinuxに近いため設定の直書きがしやすい上に、`pacman`をパッケージマネージャーに持つ
        - ArchWiKiやcommunityが充実しているので解答が探せばある 

    - `pacman`が優秀すぎる
        - 欲しいパッケージがAURまで含めると大抵ある
    
## 諸概念
- NixOS、nix-darwinはそれ単体でシステム設定とユーザー環境の定義ができるが、Home Managerをモジュールとして使用するとOS・ディストリに依存しないユーザー環境の構築ができる。
    - NixOS、nix-darwin→システム設定（GPUドライバーなどのハードウェア依存とroot権限が必要な設定等）
    - Home Manager→ユーザー環境（rootを必要としない設定やユーザー環境下のsystemd-unitの起動など）
- flakesを用いることで複数プロジェクト（ここでは各マシンの設定）を一つのリポジトリで管理することができる
    - システム設定でもユーザー情報やuid/gidなどの共用部分（個人PCの場合）を全てのマシンから参照できるのでより強力に同一環境を作ることができる
    
| 用語 | 説明 |
| :-: | :-: |
|Nix Language | 純粋関数型言語。 |
| nix | ~~パッケージマネージャー。~~ 各言語のビルドツールのラッパー兼環境構築ツール。パッケージ管理もしてくれる。 |
| Flakes | まだ実験段階。プロジェクトテンプレートの提供や依存関係の管理（flake.lockにリポジトリのハッシュなどを自動で追加してくれる）、nixpkgsの拡張(overlays)やNixOSモジュールを提供。 |
| NixOS | パッケージマネージャーにnixを用いたLinuxのディストリビューション。システム全体のロールバックが可能。 |
| nix-darwin | NixOS likeにmacOSの管理をできるようにするやつ。 |
| Home Manager | .dotfilesをnixを用いて管理するやつ。ユーザー環境限定でロールバックが可能。別ディストリでnixを使う場合はこのコマンドがメインになる。 |

## ここで作るデスクトップ環境前提

- [Sumi-Sumi/nixos-config](https://github.com/Sumi-Sumi/nixos-config)が設定
    - ハードウェアに依存しない部分は汎用化する
        - ハードウェアに依存しないシステムパッケージやユーザー環境について
    - CUI環境は`apps/common`、GUI環境は`apps/desktop`に定義
        - Home Manager側で管理するようにしており非NixOSでも使えるようにしている
    - 各マシン固有の設定は`machines/<machine_name>`
        - `machines/common`はマシン共通の設定
- 上記リポジトリは汎用性を重視したマシン設定があり、誰でも使えるようにした(README参照)
- LVM on LUKSでインストール
    - LightDMの自動ログインは使用しない （シングルユーザーの場合、冗長ではある）
- bootはsystemd-bootを使う
    - GRUBを用いればフルディスク暗号化もできるがここではしない
- zsh、neovimのプラグインはそれぞれ[zinit]()、[dein.vim]()で管理する
    - zshプラグインをmanualで管理しようと思ったら、なぜかsourceが通らないとかの問題が発生した
    - neovimはwindowsで同様の環境を作るときにnixよりもinit.vim+dein.vimで管理したほうが共通化しやすいと思ったから
   
## 環境構築Tips
### sshの設定や会社のネットワーク情報など外部に漏らしたくないconfigもgitで管理する
- `flakes`はプライベートリポジトリも`inputs`に指定できる
    - urlは`git+ssh://git@github.com/<user-name>/<repository>.git`
- プライベートリポジトリに外部に出したくない設定を分割してやればいい
    - このとき、`nixosModules`で書くと公開側でアクティブにするだけで使えるようになる
- Home ManagerをNixOS/nix-darwinのモジュールとして使っていて、作成したモジュールがHome Managerの設定を対象としている場合、インポートの位置が異なる（下記参照）。
    ```nix
    ...
    system = nixpkgs.lib.nixosSystem {
      ...
      modules = [
        ...
        home-manager.users."${user}" = {
           imports = [ (your-flake.nixosModules.your-conf) ] ++ # 作成したモジュールをhome-managerの中でインポートしてやればいい
            [(import ./home.nix)];                              # modulesの中に置くとNixOSにそんな設定は無いと怒られる
        };                                                      # (home-manager固有の設定だから当然といえば当然)
        ...                                                     # 作成したモジュールをどこかでenable=trueにするのをお忘れなく
      ];
    };
    ...
    ```

### 設定関連
- オレオレ環境になるほど設定項目が無限に増えるため、時間があるときに1. CUI環境から 2. ミニマムな環境から 3. 段階を踏んで 構築することを強くおすすめする。
    - 私は全部のせCUI環境から初めて🤔となり、デスクトップ環境の構築開始と同時に挫折しかけた。とにかくデバックがツライ。
    - 逆にDEを使うなら、インストールまでは他のディストリビューションと変わらない
- DEを使わない場合、LightDM等を用いてxsessionを開始する（Home Managerが起動コマンドを.xsessionに記述している）
    ```nix
    # xsession.nix
    xsession = {
        windowManager = {             # Not launch using dbus-launch because systemd manage dbus-user-mesage since ver.226
          command = "qtile start";    # You maybe have some probrem (ex fcitx5...) if you launch using it.
    };                            # You can see this in ArchWiki https://wiki.archlinux.jp/index.php/Systemd/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC#D-Bus

    ```
- Home Managerを用いてxsessionを管理する場合、**Home Managerがキーボードの管理をする**。
    - つまり、`ctrl:nocaps`のような設定はHome Manager側に書くことになる。
    - マウスやタッチパッド（libinput.{mouse, touchpad}）はNixOS（システム）側で管理する。
    
### アイコン関連
- pixbufのパッケージにsvg関連のモジュールが含まれていないため、\*.svgのアイコンが適切に処理できないことがある
- `gdk-pixbuf`に`librsvg`を含めるようにする
```
...
services.xserver.gdk-pixbuf.modulePackages = [pkgs.librsvg];
...
```

### ドライブ関連
- パーティションにlabelをつけるとhardware-configuration.nixがストレージに依存しなくなる→リモートリポジトリを用いてrebuildできる設定が書ける
    - UUIDだと汎用性が無いためhardware-configuration.nixをどうにかしてリポジトリに含めるor直書きする必要がある
    - nixのインストールメディアはパスワードさえ設定すればsshが使えるのでrsyncとかで作業PCに引っ張ってきてもいいかもしれない

### パッケージ固有の問題
- DiscordなどのElectronを使った一部パッケージ
    - 2022/10/17現在、Noto Font CJKにTTCが含まれなくなった？ことに起因して文字化けが発生する。
（[noto-fonts-cjk: does not display correctly since #166600 #171976](https://github.com/NixOS/nixpkgs/issues/171976)）
    - TTCを含む別のCJKフォントを使えば良い（Source Hanなど）
    - Noto Fontと共存させる場合は`configuration.nix`に以下を追加（Source Hanが優先されるようになればいい）
```nix
fonts = {
    enableDefaultFonts = true;
    fontconfig = {
      defaultFonts = {
        serif = [ "Source Han Serif" "Noto Serif CJK JP" ];
        sansSerif = [ "Source Han Sans" "Noto Sans CJK JP" ];
        monospace = [ "Source Han Mono" "Noto Sans Mono CJK JP" ];
        emoji = ["Noto Color Emoji"];
      };
    };
...
};
```
- vim/neovim
    - nvim-treesitterを使う場合、cコンパイラがないと言われて怒られるのでgccなどのコンパイラを入れておく。
    - Home-Managerでneovimを管理する場合は`programs = { neovim = {withNodeJs = true; withPython3 = true; withRuby = true;}}`を追記すると各言語で書かれたプラグインマネージャーが使える
    - dein.vimなどのnixpkgs外のプラグインマネージャー使う場合は、`.vimrc/init.vim/init.lua`に自動インストールスクリプトを追記すると楽
```vimscript
" これはdein.vimの例
" auto install dein.vim
let s:dein_dir = "~/.cache/dein"
let s:dein_url = "https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh"
if !filereadable(s:dein_dir)
  call system(printf('mkdir -p %s', s:dein_dir))
  call system(printf('curl %s > %s/install.sh', s:dein_url, s:dein_dir))
  call system(printf('sh %s/install.sh %s', s:dein_dir, s:dein_dir))
endif
```

## 環境構築（共通）

1. ISOを[NixOSのHPのダウンロード](https://nixos.org/download.html)からダウンロードする
2. [LVM on LUKSのArch Wiki](https://wiki.archlinux.jp/index.php/Dm-crypt/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%85%A8%E4%BD%93%E3%81%AE%E6%9A%97%E5%8F%B7%E5%8C%96)を参考に暗号化ディスク+LVMの環境を用意。
    - パーティションにラベルを付けておくとインストールやrebuildが楽になる
3. [NixOS Installation Guide](https://nixos.wiki/wiki/NixOS_Installation_Guide)のNixOS Configの項まで進める。
4. hardware-configuration.nixに以下を追記する。（これを忘れると起動時に暗号化ディスクが解放ができなくて詰む）
    ```nix
    ...
    boot.initrd.luks.devices = {
      luksroot = {
        device = "/dev/disk/by-uuid/<UUID>";
      };
    };
    ...
    ```
5. configuration.nixに以下を追記（これを忘れるとgitもエディタも無い何もできない環境に放り出される）
    - ラップトップでどうしても有線が使えない場合はWiFiの設定もする。（有線だと特に何もしなくてもネットにつながるので楽)
    ```nix
    ...
      environment.systemPackages = with pkgs; [ git neovim ]; # お好きなEditor
    ...
    ```
6. `passwd`でパスワードを設定後、`rsync`(ssh)等で設定configをマシンに入れる
7. nixos-installの実行
8. rebuildの実行
     - 初回rebuildはDM+WM(DE)を含めないか、autorun=falseにしてプロンプトに入るようにした方が無難
         - GPUドライバー周りでこけることがあるので、rebuild後にlightdm --test-mode --debug等で確認した方がいい