Loading...

Day 21:第三週複習 — 一個 flake.nix 管理你的 Server 與 MacBook

回顧第三週 Flakes 與 Home Manager 的核心觀念,整理如何用一份 flake.nix 同時管理 NixOS Server 與 MacBook 的跨平台開發環境。

Day 21:第三週複習 — 一個 flake.nix 管理你的 Server 與 MacBook

🗓 系列:NixOS 30 天學習之旅
📦 階段:第三階段 — Flakes 與 Home Manager(Day 15 – Day 21)
🎯 階段核心目標:現代化 Nix 流程(2024 年後的標準做法)


前言:回望第三週

過去七天,我們走過了一段密度極高的旅程。

從 Day 15 啟用 Flakes 的那一刻起,你已經徹底告別了傳統的 nix-channel 和隱式依賴管理。接著我們引入了 Home Manager 來掌控使用者層級的所有配置,學會了用模組化架構拆分日漸龐大的設定檔,最後透過 Direnv 實現了「cd 進專案就自動載入開發環境」的魔法。

到今天為止,你應該已經擁有一個結構清楚、可版本控管的 flake.nix,它能夠同時管理你的遠端 NixOS server 和本地 MacBook 開發環境 — 用同一份 code、同一套流程。

這就是 Nix 最迷人的地方 — 你的跨平台環境,變成了一份可以 git clone 的 code

讓我們花一點時間回顧、整理,然後帶著更紮實的基礎,迎接下一階段的挑戰。


Flakes 核心觀念總整理

Flakes 是目前 Nix 生態系的標準做法。經過這一週的練習,讓我們把幾個核心觀念做一次完整的梳理:

1. flake.nix — 一切的起點

flake.nix 是整個 Flake 的進入點,結構上只有兩個必要欄位:

{
  description = "My NixOS Configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, ... }: {
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./hosts/my-host/configuration.nix
        home-manager.nixosModules.home-manager
      ];
    };
  };
}
欄位用途
inputs明確宣告所有外部依賴來源及版本
outputs定義這個 Flake 對外提供什麼(system config、packages、devShells 等)

2. flake.lock — Reproducibility 的基石

flake.lock 是自動產生的鎖定檔,記錄每個 input 的確切 commit hash。這保證了:

  • 今天和明天 build 出來的結果完全一致
  • 團隊成員之間不會出現「在我的電腦上是好的」的問題
  • 升級是刻意的行為(nix flake update),不是被動發生的意外
# 更新所有 inputs
nix flake update

# 只更新特定 input
nix flake update home-manager

3. follows — 避免依賴分裂

inputs.nixpkgs.follows = "nixpkgs" 這行看似不起眼,卻至關重要。它確保 Home Manager 使用和你系統相同版本的 nixpkgs,避免出現兩套不同版本的套件集合佔用磁碟空間、甚至引發不相容問題。

4. Pure Evaluation

Flakes 預設在 pure evaluation 模式下運作,意味著 build 過程中不允許存取網路或讀取未宣告的路徑。這是 reproducibility 的重要保障 — 你的 build 結果不會因為外在環境的不同而產生差異。


不同環境的回顧

這一週最大的收穫之一,是理解了 Nix 的跨平台能力。雖然我們同時在兩種完全不同的環境上操作,但核心觀念與工具鏈是一致的。讓我們分別回顧:

MacBook 使用者的第三週成果

在 macOS 上,我們使用的是 nix-darwin 而非 NixOS。第三週結束後,你的 MacBook 環境已經具備:

  • nix-darwin:管理系統層級設定(Homebrew casks、系統偏好設定、Launch Daemons 等)
  • Home Manager:管理使用者層級的 dotfiles(Git、Zsh、Starship、編輯器等)
  • Direnv + devShell:每個專案 cd 進去就自動載入獨立的開發環境
# MacBook 上的 rebuild 指令
darwin-rebuild switch --flake .#macbook

nix-darwin 雖然不像 NixOS 能管理到 kernel 層級,但對於日常開發所需的環境配置已經非常完整。最關鍵的是 — 它和 NixOS 共用同一個 flake.nix,你不需要維護兩套完全獨立的配置。

NixOS Server 使用者的第三週成果

在遠端 NixOS server 上,我們操作的是純 CLI、無桌面環境的 headless 配置。第三週結束後,你的 server 具備:

  • Flake-based NixOS:整個系統配置由 flake.nix 驅動,版本鎖定、reproducible
  • Home Manager(headless):管理 server 上的使用者環境(shell、Git、CLI 工具),不涉及任何 GUI 應用
  • 系統服務:SSH、Nginx、PostgreSQL 等 server-side services 全部 declarative
# NixOS Server 上的 rebuild 指令
sudo nixos-rebuild switch --flake .#server

Server 環境的重點在穩定與可重現。你隨時可以在另一台 VPS 上用同一份配置快速佈署出相同的環境。


Home Manager 使用心得

Home Manager 解決了一個系統配置力不能及的問題:使用者層級的配置管理

系統配置 vs 使用者配置

在經過這一週的實際操作後,你應該已經建立了這樣的直覺:

層級管理工具範例
NixOS 系統層級configuration.nixbootloader、networking、services、firewall
macOS 系統層級nix-darwinHomebrew casks、系統偏好設定、Launch Daemons
使用者層級Home Managershell config、Git、editor、terminal 等 dotfiles

Home Manager 的常見整合模式

# 模式一:作為 NixOS module(Server 推薦)
# 在 flake.nix 的 modules 中引入 home-manager.nixosModules.home-manager

# 模式二:作為 nix-darwin module(MacBook 推薦)
# 在 flake.nix 的 modules 中引入 home-manager.darwinModules.home-manager

# 模式三:獨立使用(不綁定特定系統管理工具)
# home-manager switch --flake .#james

在我們的跨平台架構中,NixOS server 和 MacBook 各自使用對應的 module 整合方式,讓 Home Manager 與系統配置在同一個 flake.nix 中統一管理。

我最常用的 Home Manager 配置

經過一週的調整,以下是幾個實際派上用場的設定範例:

# home/default.nix
{ config, pkgs, ... }:

{
  home.username = "james";
  home.homeDirectory = "/home/james";
  home.stateVersion = "24.05";

  # Git — 再也不用手動寫 .gitconfig
  programs.git = {
    enable = true;
    userName = "James Hsueh";
    userEmail = "james@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      pull.rebase = true;
      push.autoSetupRemote = true;
    };
    delta.enable = true;  # 更好看的 diff
  };

  # Zsh — 全部 declarative
  programs.zsh = {
    enable = true;
    autosuggestion.enable = true;
    syntaxHighlighting.enable = true;
    shellAliases = {
      ll = "ls -la";
      gs = "git status";
      rebuild = "sudo nixos-rebuild switch --flake .";
    };
    oh-my-zsh = {
      enable = true;
      plugins = [ "git" "docker" "fzf" ];
      theme = "robbyrussell";
    };
  };

  # Starship prompt
  programs.starship = {
    enable = true;
    settings = {
      add_newline = true;
      character.success_symbol = "[›](bold green)";
    };
  };

  # 常用 CLI 工具
  home.packages = with pkgs; [
    ripgrep
    fd
    jq
    bat
    eza
    fzf
    lazygit
    tldr
  ];
}

一個關鍵心得:不要一次把所有 dotfiles 都遷移過來。先從 Git 和 shell 開始,確認流程順暢後再逐步加入其他工具的配置。


完整系統重建演練

這是第三階段最重要的里程碑 — 驗證你的配置確實能在全新的機器上完整重建。我們有兩台機器要照顧:遠端 NixOS server 和本地 MacBook。

重建 NixOS Server

假設你面前有一台剛裝好基本 NixOS 的新 server(只有 nixos-install 完成的初始狀態),以下是重建流程:

# Step 1: 啟用 Flakes(如果新機器還沒啟用)
# 在 /etc/nixos/configuration.nix 加入:
# nix.settings.experimental-features = [ "nix-command" "flakes" ];
# sudo nixos-rebuild switch

# Step 2: 安裝 Git(基本的新系統可能沒有)
nix-shell -p git

# Step 3: Clone 你的配置
git clone https://github.com/your-username/nix-config.git ~/nix-config
cd ~/nix-config

# Step 4: 根據新機器的硬體,更新 hardware-configuration.nix
cp /etc/nixos/hardware-configuration.nix hosts/server/

# Step 5: 重建整個系統
sudo nixos-rebuild switch --flake .#server

# Step 6: 完成!重新登入後,所有東西都到位了

重建 MacBook 開發環境

在一台全新的 MacBook 上,流程略有不同:

# Step 1: 安裝 Nix(使用官方 installer 或 Determinate Systems installer)
curl --proto '=https' --tlsv1.2 -sSf -L \
  https://install.determinate.systems/nix | sh -s -- install

# Step 2: Clone 你的配置
git clone https://github.com/your-username/nix-config.git ~/nix-config
cd ~/nix-config

# Step 3: 首次 build nix-darwin
nix build .#darwinConfigurations.macbook.system
./result/sw/bin/darwin-rebuild switch --flake .#macbook

# Step 4: 後續更新只需要
darwin-rebuild switch --flake .#macbook

用一個 flake.nix 管理兩台機器

關鍵在於 flake.nixoutputs 同時定義了 nixosConfigurations(給 NixOS server)和 darwinConfigurations(給 MacBook)。兩者共用同一份 inputs、同一個 flake.lock,確保兩台機器使用完全一致的套件版本。

共用的部分(例如 Home Manager 的 shell、Git 配置)只需要寫一次,平台特定的設定則各自放在對應的 host 目錄下。

重建後你會得到什麼?

NixOS Server:

  • ✅ 系統套件(Git、Vim、htop 等)
  • ✅ 系統服務(SSH、Nginx、PostgreSQL 等)
  • ✅ 使用者帳號與群組
  • ✅ Shell 配置(Zsh + Oh My Zsh + Starship)
  • ✅ Git 設定(.gitconfig 完全由 Home Manager 管理)
  • ✅ CLI 工具(ripgrep、fd、jq、bat 等)

MacBook:

  • ✅ Homebrew casks(透過 nix-darwin 管理)
  • ✅ Shell 配置(與 server 共用同一份 Home Manager 設定)
  • ✅ Git 設定(跨平台一致)
  • ✅ 編輯器配置(VS Code extensions 與 settings)
  • ✅ 開發環境(透過 Direnv 自動載入的 devShell)
  • ✅ CLI 工具(與 server 共用同一份清單)

從零到完整重建,NixOS server 大約 5–15 分鐘,MacBook 大約 10–20 分鐘(取決於網速和需要下載的套件數量)。

想像一下:你的 server 掛了、MacBook 被偷了、或是公司發了一台新的工作機。你不再需要花一整天從頭設定環境,而是 git clone + 一條指令就能回到原本熟悉的工作狀態。


目前的配置架構總覽

經過三週的迭代,你的 repository 結構應該已經從一個單檔演化成跨平台的模組化架構:

nix-config/
├── flake.nix              # 進入點:定義 inputs 與 outputs
├── flake.lock             # 鎖定所有依賴版本

├── hosts/                 # 每台機器一個目錄
│   ├── server/            # 遠端 NixOS server
│   │   ├── configuration.nix    # server 系統配置
│   │   └── hardware-configuration.nix  # 硬體相關設定
│   └── macbook/           # 本地 MacBook
│       └── configuration.nix    # nix-darwin 系統配置

├── modules/               # 可複用的系統模組
│   ├── nixos/             # NixOS 專用模組
│   │   ├── base.nix       # 基礎設定(時區、語系、基本套件)
│   │   ├── networking.nix # 網路與防火牆
│   │   ├── docker.nix     # Docker / container 相關
│   │   └── services/
│   │       ├── ssh.nix
│   │       └── nginx.nix
│   └── darwin/            # nix-darwin 專用模組
│       ├── base.nix       # macOS 基礎設定
│       ├── homebrew.nix   # Homebrew casks 管理
│       └── system.nix     # macOS 系統偏好設定

├── home/                  # Home Manager 配置(跨平台共用)
│   ├── default.nix        # 使用者層級進入點
│   ├── shell.nix          # Zsh + Starship
│   ├── git.nix            # Git 配置
│   ├── editor.nix         # 編輯器設定
│   └── dev-tools.nix      # CLI 開發工具

└── overlays/              # 自訂套件覆寫
    └── default.nix

對應的 flake.nix

{
  description = "My Cross-Platform Nix Configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

    # macOS 專用的 nixpkgs(unstable 通常對 darwin 支援較好)
    nixpkgs-darwin.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nix-darwin = {
      url = "github:LnL7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs-darwin";
    };
  };

  outputs = { self, nixpkgs, nixpkgs-darwin, home-manager, nix-darwin, ... }:
  {
    # ── NixOS Server ──────────────────────────────────
    nixosConfigurations = {
      server = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./hosts/server/configuration.nix
          ./modules/nixos/base.nix
          ./modules/nixos/networking.nix
          ./modules/nixos/docker.nix

          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users.james = import ./home;
          }
        ];
      };
    };

    # ── MacBook(nix-darwin)───────────────────────────
    darwinConfigurations = {
      macbook = nix-darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        modules = [
          ./hosts/macbook/configuration.nix
          ./modules/darwin/base.nix
          ./modules/darwin/homebrew.nix

          home-manager.darwinModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users.james = import ./home;
          }
        ];
      };
    };

    # ── 獨立 Home Manager(不綁定特定系統管理工具時使用)──
    homeConfigurations = {
      "james" = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages."x86_64-linux";
        modules = [ ./home ];
      };
    };

    # ── 開發用 devShell ───────────────────────────────
    devShells."x86_64-linux".default =
      nixpkgs.legacyPackages."x86_64-linux".mkShell {
        packages = with nixpkgs.legacyPackages."x86_64-linux"; [ nil nixpkgs-fmt ];
      };

    devShells."aarch64-darwin".default =
      nixpkgs-darwin.legacyPackages."aarch64-darwin".mkShell {
        packages = with nixpkgs-darwin.legacyPackages."aarch64-darwin"; [ nil nixpkgs-fmt ];
      };
  };
}

架構設計的幾個原則

  1. 跨平台共用 Home Manager:shell、Git、CLI 工具等使用者配置寫一次,NixOS 和 macOS 共用
  2. 平台特定設定各自分離modules/nixos/ 放 NixOS 專用模組,modules/darwin/ 放 macOS 專用模組
  3. 同一個 flake.lock 鎖定版本:確保兩台機器使用一致的套件版本
  4. homeConfigurations 作為備用方案:當目標機器沒有 NixOS 也沒有 nix-darwin 時,仍可獨立使用 Home Manager

常見問題與解法

這一週下來,你可能遇到了一些惱人的問題。以下是幾個常見情境與對應的解法:

Q1:error: experimental feature 'flakes' is disabled

剛接觸 Flakes 最常碰到的錯誤。解法有兩種:

# 臨時方案:加上 --extra-experimental-features flag
nix --extra-experimental-features "nix-command flakes" build

# 永久方案:在 configuration.nix 中啟用
# nix.settings.experimental-features = [ "nix-command" "flakes" ];

Q2:flake.lock 要不要 commit 進 Git?

要,而且一定要。 flake.lock 就是你的「依賴版本快照」。沒有它,reproducibility 就無從談起。把它想成 Node.js 的 package-lock.json 或 Rust 的 Cargo.lock

Q3:Home Manager 配置改了但沒生效?

幾個常見原因:

# 1. 忘了 rebuild
# NixOS Server:
sudo nixos-rebuild switch --flake .#server
# MacBook:
darwin-rebuild switch --flake .#macbook

# 2. Home Manager 的 stateVersion 設錯了
# 確認 home.stateVersion 和你的 NixOS 版本對應

# 3. 某些設定需要重新登入才會生效
# 例如 shell 的變更,logout 再 login 即可

Q4:Direnv 進入目錄沒有自動載入?

# 確認 .envrc 檔案存在且內容正確
cat .envrc
# 應該顯示:use flake

# 確認已經 allow 這個目錄
direnv allow

# 確認 shell 有載入 direnv hook
# Home Manager 啟用後通常會自動設定
# 如果沒有,手動確認 .zshrc 有 eval "$(direnv hook zsh)"

Q5:nix flake update 後系統壞了?

這就是 flake.lock 的價值所在。直接用 Git 回復即可:

# 回到上一版的 flake.lock
git checkout HEAD~1 -- flake.lock

# 重新 rebuild(NixOS Server)
sudo nixos-rebuild switch --flake .#server
# 或(MacBook)
darwin-rebuild switch --flake .#macbook

Q6:Build 太慢、磁碟空間不夠?

# 清理舊的 generation
sudo nix-collect-garbage -d

# 如果只想清理超過 7 天的舊資料
sudo nix-collect-garbage --delete-older-than 7d

# 最佳化 Nix store(去除重複檔案)
nix store optimise

第三階段總結

讓我們用一張表來回顧這七天的旅程:

Day主題核心收穫
15啟用 Flakes告別 nix-channel,擁抱 flake.nix + flake.lock 的明確依賴管理
16Flake 結構解析理解 inputs / outputs 架構,學會定義 nixosConfigurations
17引入 Home Manager將使用者層級配置(dotfiles)納入 declarative 管理
18模組化架構拆分龐大的配置檔為可複用的模組,建立清楚的目錄結構
19Direnv 與 devShell實現「進入目錄就自動載入開發環境」的工作流
20實戰整合將所有配置整合為一個完整的 Flake,並驗證可在新機器上重建
21第三週複習回顧、整理、鞏固所有觀念

你在第三階段建立的能力

回想第一階段結束時,你會手動編輯 configuration.nixnixos-rebuild switch。第二階段,你學會了 Nix 語言的核心語法和套件管理的進階技巧。

現在,經過第三階段,你已經具備了:

  1. 用 Flakes 管理完整系統配置 — 明確的 inputs、可鎖定的版本、pure evaluation
  2. 用 Home Manager 管理使用者環境 — 從 Git config 到 shell aliases,全部 declarative
  3. 模組化思維 — 將配置拆分為邏輯清楚、可複用的模組
  4. 自動化開發環境 — Direnv + devShell,讓每個專案擁有獨立、可重現的環境
  5. 跨平台管理能力 — 一個 flake.nix 同時管理 NixOS server 和 MacBook 開發環境

用一句話總結第三階段的精神:

你的 NixOS server 和 MacBook,現在都是同一個 Git repository。


第四階段預告:進階實務(Day 22 – Day 28)

前三週我們打下了紮實的基礎。接下來的第四階段,將進入真正的進階實務領域。

以下是即將探索的主題:

Day預定主題簡介
22Secrets 管理使用 sops-nixagenix 安全管理密碼、API key 等敏感資訊
23自訂 Overlay修改或覆寫 nixpkgs 中的套件,打造客製化的套件集
24打包自己的軟體學會用 mkDerivation 打包你自己的應用程式
25CI/CD 整合在 GitHub Actions 中使用 Nix,實現 reproducible 的 CI pipeline
26多機管理與佈署使用 deploy-rscolmena 管理多台 NixOS 機器
27效能調校Binary cache、Cachix、build 加速技巧
28第四週複習進階實務回顧與最佳實踐總整理

第四階段的難度會明顯提升,但有了前三週的基礎,你已經準備好了。


結語

走到 Day 21,你已經完成了 NixOS 學習旅程的 70%。

如果你在這個時間點回頭看 Day 1 的自己,會發現那個對 /nix/storedeclarative 還感到困惑的你,現在已經能夠自信地管理一整套模組化的系統配置了。

第三階段的核心訊息很簡單:現代化的 Nix 工作流 = Flakes + Home Manager + 模組化架構。無論你是在 NixOS server 還是 MacBook 上,這套方法論都一體適用。

好好休息一下,整理你的 flake.nix,把它推上 GitHub。明天開始,我們將挑戰更進階的實務主題。

我們明天見! 🚀


📚 延伸閱讀