EXODUS

注册/登录

by 鋅厘 in 2024-08-23

我在 NixOS 踩坑 tmpfs on root 的記錄

我在 NixOS 轉向 tmpfs on root 的過程中碰到了很多坑, 有些是 NixOS 自己的問題,而有些是我本身的許多需求耦合在一起的結果。

為什麼要 tmpfs on root?

先說 tmpfs on root 的好處:tmpfs 讓我的根目錄和家目錄僅用內存裝載, 就能保證每次都能有一個相對乾淨的根目錄和家目錄, 不會因為一些東西的緣故讓系統的狀態積重難返。

所有系統的狀態和需要保留的文件都可以額外通過 impermanence 模組指定硬盤的位置存放, 以持久化的方式保存下來,在運行的時候通過 bindfs 的方式硬連接到指定的位置。

如何做到?

很好,現在目標定下來了。接下來就是把大象裝進冰箱了。

簡單地說,切換到 tmpfs 需要我僅僅把系統狀態依賴於硬盤的持久化目錄。 也就是說,我的需求是:

  • 保存 NixOS 必要的運行文件,即原有系統裡的 /nix/store/nix/var

  • 保存 Linux 和 systemd 和一些依賴家目錄配置的應用程序的必要的運行時文件, 例如 /etc/adjtime/var/lib/systemd/timers 等等。 這些必要的系統狀態我選擇放到 /nix/persist 中去。

  • 保存一些我需要使用的文件,比如動漫高手看的高手動漫, 這些文件我選擇放到 /nix/storage 裡去。

需要注意的一點是,持久化不代表這些文件是只能被讀取而不能寫的。 相反,持久化意味著所有系統的狀態都會於 NixOS 運行時在上述的指定位置修改。

總結一下現在的需求的實現目標,我希望我的硬盤是以下這個樣子:

/dev/nvme1n1
|- /dev/nvme1n1p1 # 存放 /boot 信息
|- /dev/nvme1n1p3 # SWAP 預留分區
|- /dev/nvme1n1p2 # 系統啟動時掛載為 /nix 目錄
   |              # 第一部分: NixOS 必要的運行文件
   |- store       # - 原來的 /nix/store
   |- var         # - 原來的 /nix/var
   |              # 第二部分: Linux, systemd, 以及
   |- persist     # 一些依賴家目錄的應用必要的運行文件
   |  |- var/lib  # - Linux, systemd 以及一些系統級服務
   |  |           #   運行時需要的數據. 例如: bluetooth
   |  |           #   會在這裡記錄已連接的藍牙設備.
   |  |- var/log  # - 日誌文件
   |  |- etc/     # - 上述系統相關程序的配置. 例如:
   |  |           #   NetworkManager 會在此存放配置.
   |  |- home/shinri/.local
   |  |- home/shinri/.config
   |              # - 一些應用依賴家目錄來存放配置
   |- storage     # 第三部分: 放一些我想放的東西
      |- OneDrive
      |- Collections
      ...

這樣,當根目錄是 tmpfs 時,系統就會形成以下的樣子(=>表示掛載關係)

/ => tmpfs
|- nix => /dev/nvme1n1p2
|  |- store
|  |- var
|  |- persist
|  |- storage
|- home/shinri
|  |- .local  => /nix/persist/home/shinri/.local
|  |- .config => /nix/persist/home/shinri/.config
|  |- Collections => /nix/storage/Collections
|  |- OneDrive => /nix/storage/OneDrive
|  ...
|- var
|  |- lib/bluetooth => /nix/persist/var/lib/bluetooth
|  |- log => /nix/persist/var/log
|  ...
|- etc
|  |- NetworkManager/system-connections
|  |   => /nix/persist/etc/NetworkManager/system-connections
|  ...
... 

但實際上,在動工之前,我的硬盤是這個樣子:

/ => /dev/nvme1n1p2
|- nix
|  |- store
|  |- var
|- home
|- etc
|- var
|- ...

於是,為了讓硬盤達到我希望的狀態,我需要做的就是: 先愚公移山,把當前的硬盤改造成上面我想要的 layout。

愚公移山

在備份了一些小且重要的配置文件之後, 我需要做的就是把需要持久化的文件拷貝到 /persist/storage 下, 如果類似 100G 以上的動漫,太大的話就移動過去。 結合 impermanence 模組的聲明式配置, 我可以在不掛 tmpfs 的情況下在當前系統調試這樣做是否符合我的想法。 沒錯,持久化這些目錄比掛載 tmpfs 要先開始, 因為我要不停地觀察 mountlsblk 是否給出符合我需求的掛載。

雖然這一步說起來很簡單,但是做起來很複雜,花了我兩到三個整天的時間。 選擇細粒度地梳理哪些需要被持久化是很痛苦的, 有時候可能會發現既有的 NixOS 或 home-manager 的 options 可以優先調用, 因此我花費了大量的時間。

再次提醒,備份,備份,備份。 在這個過程中我有好幾次膽戰心驚地誤刪了目標的文件,都是靠備份救回來的。 比如我一開始不太熟悉 impermanence, 直接 nixos-rebuild 把空目錄掛載到目標上去, 目標目錄直接變空; 或者有好幾次我直接刪掉了本來應該放在 /persist 和 /storage 的文件。

備份,備份,備份。

利用 NixOS LiveCD 切換根目錄文件系統為 tmpfs

等硬盤的 layout 符合我的預期以後,接下來就是把冰箱門關起來了。 然而因為我用了很多 flake 模組, 導致 nixos-rebuild (以下簡稱 rebuild) 的過程本身就經歷了一個疊床架屋的運行過程:

  1. rebuild 會調用我本地 Git 倉庫的 flake

  2. 然後將我 sops-nix 模組配置的密碼利用 放在家目錄的私鑰 解密到 /run/secrets-rendered

  3. 最後當構建完之後 lanzaboote 模組調用 /etc/secureboot 對新的啟動項進行簽名,以符合 secureboot 的需求。

以下是一些在 LiveCD 環境下,從 non-tmpfs 切換到 tmpfs 的踩坑的記錄, 不包括前置的作業流程,比如挑選需要保留的文件, 這個挑選過程請看上文的「愚公移山」一節。

  1. 在僅僅有 homenix 目錄的情況下 nixos-enter (以下簡稱 enter )沒有辦法在 /mnt 成功, 提示 not a NixOS installation。

    解決方式是在 /mnt/etc/NIXOS 創建一個空文件。

  2. rebuild 本身的問題是會用到 $SUDO_USER 然後提示說無法使用 PAM authentication。

    解決方式是把這個環境變量刪掉。

  3. 基於 flake 的 rebuild 會因為找不到 git 報錯。

    解決方式是直接開一個有 git 的 nix-shell 。

    nix-shell -p git
    
  4. 因為 enter 給的環境的 hostname 變成了 nixos, 所以直接調用 rebuild 會提示找不到 flake。

    解決方式是要么改 hostname,要么改 flake。

    hostname <your_name>
    
  5. 使用 impermanence 模組的時候要特別注意一點,如果原來規定的目錄 (比如 /persist )要改成其他目錄(比如 /nix/persist )的時候, 由於 enter 調用的是你既有的系統構建,所以 impermanence 的掛載會失敗, 在 rebuild 過程中必要的文件需要手動複製或者掛載。

  6. rebuild 過程配置 secureboot 的時候,會因為前述第 5 點無法裝載 lanzaboote 模組對目標內核簽名的文件, 導致沒有辦法構建成功並灌到 bootloader 裡去[1]。 解決辦法是

    cp -ar /nix/persist/etc/secureboot/ /etc/.
    # -a 代表保留權限信息
    

    順帶一提,這個花了我快一個小時。 報錯根本屁用沒有,提示文件找不到卻不告訴我哪個文件找不到, 我只能用 strace 一個個去看,修的過程 LiveCD 還炸了, 於是前面第 1-6 點我還要再弄一遍。

  7. 系統在掛載 tmpfs 的時候無法接受 default 選項, 會直接卡死在 NixOS stage 1。

    解決方法是把該死的 default 從 disko 模組的配置裡刪掉。

    順帶一提,這個也花了我一個多小時,因為要進 LiveCD 改配置的前置作業很麻煩, 前面 1-6 點都要解決,而且 LiveCD 裝載得慢得要死。

  8. enter 時由於我還有 sops-nix 模組用來加載加密信息, 而又由於前述第 5 點無法裝載 ~/.ssh,會導致報錯。 雖然還是可以進 enterchroot 環境, 但我把 nix.conf 的 GitHub API Token 也給 sops-nix 管理了, 如果需要互聯網可能就比較麻煩,這個時候解決這個問題就需要手動複製密鑰過去。

    cp -ar /nix/persist/home/shinri/.ssh/ /home/shinri
    

後記

這是我最 nix 頭腦體操的一次經歷了。 從發想到完整實行,時間跨度竟然有一個多月, 再加上這週集中排查 /var /etc ~/.config ~/.local, 再調了一下沒怎麼用過的 plasma-manager 模組, 簡直是一石激起千層浪。

不過好在終於把我既有的東西都搬到這裡了,也避免了重裝系統。 在這個過程中找資料的時候沒有看到解決類似問題的文章, 因此我把這個折騰的過程寫在這裡供想要轉向 tmpfs on root 同時又不想重裝系統的讀者參考。

如果你有問題,歡迎通過 Matrix 與我聯繫: @shinri:zince.net。 如果只是想打個招呼、混個臉熟也非常歡迎!

感謝

感謝 @olmo:chillin.gs@2xsaiko:tchncs.de 這兩位 在 NixOS 的 Matrix 群組幫助我解答部分疑惑

I want to thank @olmo:chillin.gs and @2xsaiko:tchncs.de for helping me initiating the idea and solving my questions.

感謝「gyara」向我推薦 tmpfs-on-root 的做法, 並持續幫助我, 在這個過程中為我答疑解惑。 我的NixOS 配置有很多 參考了他的配置