Loading...

Day 4:版本管理 — Generation 與 build-vm 的安全網

深入理解 NixOS 的 Generation 機制與版本管理,學會使用 build-vm 在虛擬機中安全測試配置變更,避免系統損壞風險。

Day 4:版本管理 — Generation 與 build-vm 的安全網

🗂️ 系列:NixOS 30 天學習之旅 📦 階段:第一階段 — 基礎與生存守則 (Day 1 – Day 7) 🎯 階段核心目標:理解聲明式 (Declarative) 配置與不可變性


前言:為什麼版本管理對 NixOS 特別重要?

在傳統的 Linux 發行版上,每次安裝、更新或移除套件,都是對系統進行「就地修改 (in-place mutation)」。一旦改壞了什麼,想回到上一個正常的狀態,往往得靠記憶、靠備份、靠運氣。

NixOS 從根本上解決了這個問題。

還記得 Day 2 提過的聲明式配置嗎?每一次你執行 nixos-rebuild switch,NixOS 就會根據你的 configuration.nix 產生一個全新的系統 generation(世代)。這些 generation 不會彼此覆蓋,而是共存在系統中。你可以隨時列出、切換、甚至刪除任何一個 generation。

更棒的是,NixOS 還提供了 nixos-rebuild build-vm 這個指令,讓你在修改配置之前,先用虛擬機 (VM) 跑一遍——完全不動到你的主機

今天,我們就來學會這張「安全網」。


Generation 的概念

每次 rebuild,就是一個新世代

在 NixOS 的世界觀裡,系統狀態不是「現在長什麼樣子」,而是「你告訴它應該長什麼樣子」。每次你執行 nixos-rebuild switch,系統就會:

  1. 讀取 /etc/nixos/configuration.nix(及其 imports)
  2. 根據配置計算出完整的系統 closure
  3. 建構所有需要的套件與設定檔
  4. 產生一個新的 generation,並將它設為「目前啟用的版本」
  5. 更新 bootloader,把這個 generation 加入開機選單

這就像是 Git 的 commit:每個 generation 都是系統在某個時間點的完整快照 (snapshot),而且不可變 (immutable)。

Generation 的存放位置

NixOS 的 generation 資訊存放在 /nix/var/nix/profiles/ 目錄下:

ls -la /nix/var/nix/profiles/system-*

你會看到類似這樣的輸出:

system-1-link -> /nix/store/abc123...-nixos-system-nixos-24.05
system-2-link -> /nix/store/def456...-nixos-system-nixos-24.05
system-3-link -> /nix/store/ghi789...-nixos-system-nixos-24.05

每個 system-N-link 就是一個 generation,指向 /nix/store 裡的一個不可變路徑。


列出與管理 Generations

列出所有 generation

最直覺的方式是使用 nix-env 搭配 --list-generations 旗標:

sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

輸出大概長這樣:

   1   2024-01-15 10:30:00   
   2   2024-01-16 14:22:33   
   3   2024-01-20 09:15:47   (current)

標示 (current) 的就是你目前正在使用的 generation。

查看兩個 generation 之間的差異

想知道上次 rebuild 到底改了什麼嗎?可以用 nix profile diff-closures 來比較:

nix profile diff-closures --profile /nix/var/nix/profiles/system

這個指令會列出每個 generation 之間新增、移除、升級的套件,非常適合用來追蹤變更歷史。

刪除舊的 generation

隨著時間推移,generation 會越來越多,佔用 disk 空間。你可以用以下指令清理:

# 刪除 30 天前的 generation
sudo nix-collect-garbage --delete-older-than 30d

或是更精確地指定要保留哪些:

# 只保留最近 5 個 generation
sudo nix-env --profile /nix/var/nix/profiles/system --delete-generations +5

⚠️ 注意:刪除 generation 之後,你就無法再 rollback 到那些版本了。建議至少保留最近 2–3 個 generation 作為安全緩衝。

刪除 generation 之後,記得執行 garbage collection 來真正釋放 disk 空間:

sudo nix-collect-garbage

nixos-rebuild build-vm 實戰

為什麼需要 build-vm?

想像一下這個情境:你打算在 configuration.nix 裡啟用一個新的 service,比如 Nginx。你不太確定設定對不對,也擔心會不會影響到目前正常運作的系統。

傳統做法是「先改了再說,壞了再修」。但在 NixOS 上,你有更優雅的選擇:

sudo nixos-rebuild build-vm

這個指令會根據你目前的 configuration.nix,建構一個完整的 NixOS 系統,然後包裝成一個 QEMU 虛擬機映像。你可以直接啟動這台 VM,在裡面驗證你的配置是否正確——完全不影響你的主機

第一次使用 build-vm

讓我們來實際操作一次。假設你想在系統裡加入 htop 這個套件,並且啟用 Nginx:

首先,編輯你的 configuration.nix

# /etc/nixos/configuration.nix
{ config, pkgs, ... }:

{
  # ... 既有的設定 ...

  # 加入 htop
  environment.systemPackages = with pkgs; [
    vim
    git
    htop  # 新增這行
  ];

  # 啟用 Nginx
  services.nginx.enable = true;
  services.nginx.virtualHosts."localhost" = {
    root = "/var/www";
  };
}

接著,不要急著 switch,先用 build-vm 試試看:

sudo nixos-rebuild build-vm

建構完成後,你會看到類似這樣的輸出:

building the system configuration...
Done. The virtual machine can be started by running
  /nix/store/xxxx-nixos-vm/bin/run-nixos-vm

啟動虛擬機

直接執行輸出的路徑就能啟動 VM:

sudo /nix/store/xxxx-nixos-vm/bin/run-nixos-vm -nographic

QEMU 會開啟一個視窗,裡面跑著一台完整的 NixOS 系統。你可以在裡面登入、檢查套件是否安裝、service 是否正常啟動。

💡 小提醒build-vm 預設會使用你的 configuration.nix,但 VM 的 root 密碼預設是空的(直接按 Enter 就能登入)。

如果需要指定密碼,可以在配置中加入:

users.users.root.initialPassword = "test";

如果你覺得懶得每次測試都要打密碼,可以在 confiuration.nix 中新增以下設定

 services.getty.autologinuser="root";

如果需要退出 vm 可以執行以下指令

poweroff
# 或者
shutdown -h now

在 VM 中測試配置變更

啟動 VM 之後,你可以進行以下驗證:

確認套件已安裝

# 在 VM 裡面執行
which htop
htop --version

確認 service 狀態

# 在 VM 裡面執行
systemctl status nginx
curl http://localhost

確認系統設定

# 在 VM 裡面執行
nixos-version
cat /etc/os-release

如果一切符合預期,你就可以放心地回到主機上執行:

sudo nixos-rebuild switch

如果發現問題,只要關掉 VM 視窗,修改 configuration.nix,再重新 build-vm 即可。完全零風險。

build-vm 的限制

雖然 build-vm 非常實用,但有幾點要留意:

  • 硬體相關的設定(如 GPU driver、Wi-Fi 驅動程式)在 VM 裡可能無法正確測試
  • 磁碟掛載 (mount)分割區 (partition) 等設定在 VM 裡不會反映真實硬體
  • VM 的效能會比實機慢,尤其是圖形介面
  • 需要安裝 QEMU(NixOS 預設通常已包含)

Rollback 回滾實作

情境:switch 之後發現問題

假設你已經執行了 nixos-rebuild switch,結果新的配置有問題——也許某個 service 啟動失敗,或是桌面環境壞掉了。別緊張,NixOS 的 rollback 機制讓你可以秒速還原。

方法一:從 bootloader 回滾

最簡單的方式:重新開機,在 GRUB 選單中選擇上一個 generation。

GRUB 選單會列出所有可用的 generation,格式大概像這樣:

NixOS - Configuration 3 (current)
NixOS - Configuration 2
NixOS - Configuration 1

選擇上一個可正常運作的 generation,就能立刻回到那個狀態。

方法二:使用指令回滾

如果系統還能正常進入,可以直接用指令切換:

# 切換到上一個 generation
sudo nixos-rebuild switch --rollback

這個指令會立刻將系統切換到前一個 generation,不需要重新開機。

方法三:切換到指定的 generation

如果你想跳回更早的某個 generation,可以這樣操作:

# 先列出所有 generation
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

# 切換到指定的 generation(例如第 2 個)
sudo /nix/var/nix/profiles/system-2-link/bin/switch-to-configuration switch

切換完成後,系統就會回到 generation 2 的狀態,包含當時的所有套件版本、service 設定、系統參數等等。

完整的安全工作流程

綜合今天學到的內容,推薦的配置變更流程如下:

   修改 configuration.nix


   nixos-rebuild build-vm


      啟動 VM 測試
      ┌────┴────┐
      │         │
   通過 ✅    失敗 ❌
      │         │
      ▼         ▼
  switch     修改配置
              再測試

用一行指令表示就是:

# 1. 先測試
sudo nixos-rebuild build-vm

# 2. 確認沒問題再正式套用
sudo nixos-rebuild switch

# 3. 如果出問題,立即回滾
sudo nixos-rebuild switch --rollback

補充:rebuild 的幾種模式

在往下走之前,讓我們整理一下 nixos-rebuild 常用的幾個子指令:

指令說明
nixos-rebuild switch建構新 generation 並立即切換,同時更新 bootloader
nixos-rebuild boot建構新 generation 並更新 bootloader,但不立即切換(下次開機才生效)
nixos-rebuild test建構新 generation 並立即切換,但不更新 bootloader(重開機會回到舊版)
nixos-rebuild build只建構,不切換也不更新 bootloader(純粹確認能不能成功 build)
nixos-rebuild build-vm建構一個可在 QEMU 中執行的 VM,用來測試配置

每個指令適用於不同的情境:

  • 日常更新switch
  • 想先確認能 buildbuild
  • 想在 VM 裡測試build-vm
  • 想下次開機才套用boot
  • 想暫時測試但重開機能還原test

小結

今天我們學到了 NixOS 版本管理的三大支柱:

  1. Generation 機制:每次 rebuild 都會產生一個不可變的系統快照,讓你永遠有回頭路。
  2. build-vm 測試:在正式套用之前,用虛擬機驗證你的配置,零風險實驗。
  3. Rollback 回滾:就算 switch 之後出了問題,一個指令就能回到上一個正常的狀態。

這三個機制加在一起,構成了 NixOS 最讓人安心的「安全網」。你再也不用害怕「改壞系統」這件事了——因為每一個改動都是可追蹤、可測試、可還原的。

明日預告

Day 5:Nix Language 快速入門(上)

到目前為止,我們一直在修改 configuration.nix,但對裡面那些 { config, pkgs, ... }: 之類的語法還是似懂非懂。明天我們就來正式認識 Nix language——NixOS 背後的函數式語言。我們會從基本型別、變數綁定 (let-in) 開始,帶你讀懂那些看起來有點陌生的語法。


📌 今日指令速查表

# 列出所有 generation
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

# 比較 generation 差異
nix profile diff-closures --profile /nix/var/nix/profiles/system

# 在 VM 中測試配置
sudo nixos-rebuild build-vm

# 正式套用配置
sudo nixos-rebuild switch

# 回滾到上一個 generation
sudo nixos-rebuild switch --rollback

# 清理舊的 generation
sudo nix-collect-garbage --delete-older-than 30d