Loading...

Day 30:畢業考 — 為 Nixpkgs 貢獻你的第一個 Package

NixOS 30 天旅程的畢業挑戰:完整走過為 Nixpkgs 貢獻 package 的流程,從 fork、撰寫 derivation 到提交 PR,回饋開源社群。

Day 30:畢業考 — 為 Nixpkgs 貢獻你的第一個 Package

🗓 系列:NixOS 30 天學習之旅
📦 階段:第四階段 — 工程師進階實務(Day 22 – Day 30)
🎯 階段核心目標:佈署、自動化、安全性與貢獻


前言:最後一天,畢業挑戰

走到 Day 30,你已經不是當初那個對 /nix/store 一頭霧水的新手了。

過去 29 天裡,你學會了 declarative configuration、Flakes、overlay、module system、Home Manager、CI/CD pipeline、remote deployment…這些知識加在一起,代表你已經具備了一位 NixOS power user 的基本素養。

但真正讓一個工程師「畢業」的,不是讀了多少文件,而是你有沒有能力回饋社群

今天的畢業考只有一個任務:為 Nixpkgs 貢獻一個 package

這不是什麼高不可攀的事。Nixpkgs 是全世界最大的套件倉庫之一,截至目前已經有超過 100,000 個 packages,而這些全都是由社群成員一個一個 PR 堆起來的。你寫的下一個 package,就可能成為其中之一。

如果你暫時找不到適合打包的軟體,本文也會提供另一條路線:徹底把你的工作流程用 Nix 自動化,作為這趟旅程的收尾。


為 Nixpkgs 貢獻的完整流程

在動手寫 code 之前,先搞懂整個貢獻流程。Nixpkgs 是一個託管在 GitHub 上的 monorepo,所有的 package definition 都在這個 repository 裡面。

Step 1:Fork 與 Clone

# Fork nixpkgs 到你的 GitHub 帳號後
git clone https://github.com/<your-username>/nixpkgs.git
cd nixpkgs

# 追蹤上游 repository
git remote add upstream https://github.com/NixOS/nixpkgs.git
git fetch upstream

Nixpkgs 的 repository 非常龐大(commit history 超過 60 萬筆),clone 的時候建議加上 --depth=1 做 shallow clone,後續再依需要 fetch 更多歷史:

git clone --depth=1 https://github.com/<your-username>/nixpkgs.git

Step 2:建立 Feature Branch

Nixpkgs 的慣例是基於 master branch 開新的 branch:

git checkout -b add-my-awesome-tool upstream/master

Branch 命名沒有嚴格規定,但建議用清楚的前綴,例如 add-<package-name>update-<package-name>

Step 3:確認你要打包的軟體

在動手之前,先確認這個軟體還沒被打包過:

# 在 nixpkgs repo 裡搜尋
grep -r "pname = \"my-tool\"" pkgs/

# 或者用 nix search(如果你已經有可用的 nixpkgs channel)
nix search nixpkgs my-tool

同時也到 Nixpkgs pull requests 搜尋,確認沒有人正在進行同樣的工作。


實戰:為一個開源軟體撰寫 Package

我們用一個假想的 CLI 工具 hello-nix 作為範例,示範完整的打包流程。這是一個用 Rust 寫的命令列工具,source code 託管在 GitHub 上。

決定 Package 放在哪裡

Nixpkgs 有自己的目錄結構慣例:

pkgs/by-name/<前兩個字母>/<package-name>/package.nix

這是 Nixpkgs 目前推薦的 by-name 結構。以 hello-nix 為例:

pkgs/by-name/he/hello-nix/package.nix

撰寫 package.nix

以下是一個典型的 Rust package 定義:

# pkgs/by-name/he/hello-nix/package.nix
{
  lib,
  fetchFromGitHub,
  rustPlatform,
}:

rustPlatform.buildRustPackage rec {
  pname = "hello-nix";
  version = "1.2.3";

  src = fetchFromGitHub {
    owner = "example-org";
    repo = "hello-nix";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  cargoHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";

  meta = {
    description = "A friendly CLI tool built with Rust";
    homepage = "https://github.com/example-org/hello-nix";
    license = lib.licenses.mit;
    maintainers = with lib.maintainers; [ your-github-username ];
    mainProgram = "hello-nix";
  };
}

各欄位解說

欄位說明
pnamePackage 名稱,必須和目錄名稱一致
version上游的版本號
src從哪裡抓取 source code,fetchFromGitHub 是最常見的 fetcher
hashSource tarball 的 hash,用來確保 reproducibility
cargoHashRust dependencies 的 hash(若是 Go 專案則使用 vendorHash
metaPackage 的 metadata,包含描述、授權、維護者等資訊

取得正確的 Hash

第一次寫 package 時,你不會知道正確的 hash。技巧是先填一個假的 hash,然後讓 Nix 告訴你正確值:

# 先用一個空的 hash
# hash = lib.fakeHash;

# 嘗試 build,Nix 會報錯並告訴你正確的 hash
nix-build -A hello-nix

錯誤訊息中會包含 got: sha256-XXXXX... 的字樣,把那串 hash 貼回 package.nix 即可。cargoHash 也是同樣的做法。

處理不同語言的 Build System

不同語言有不同的 builder,以下是常見的幾種:

Go 專案:

{ lib, fetchFromGitHub, buildGoModule }:

buildGoModule rec {
  pname = "my-go-tool";
  version = "0.5.0";

  src = fetchFromGitHub {
    owner = "example";
    repo = "my-go-tool";
    rev = "v${version}";
    hash = "sha256-...";
  };

  vendorHash = "sha256-...";

  meta = {
    description = "A Go CLI tool";
    homepage = "https://github.com/example/my-go-tool";
    license = lib.licenses.asl20;
    maintainers = with lib.maintainers; [ your-github-username ];
  };
}

Python 專案:

{
  lib,
  fetchFromGitHub,
  python3Packages,
}:

python3Packages.buildPythonApplication rec {
  pname = "my-python-tool";
  version = "2.0.0";
  pyproject = true;

  src = fetchFromGitHub {
    owner = "example";
    repo = "my-python-tool";
    rev = "v${version}";
    hash = "sha256-...";
  };

  build-system = [ python3Packages.setuptools ];

  dependencies = [
    python3Packages.requests
    python3Packages.click
  ];

  meta = {
    description = "A Python CLI tool";
    homepage = "https://github.com/example/my-python-tool";
    license = lib.licenses.mit;
    maintainers = with lib.maintainers; [ your-github-username ];
    mainProgram = "my-python-tool";
  };
}

本機測試

寫完 package 定義後,先在本機 build 並測試:

# 在 nixpkgs repo 根目錄執行
nix-build -A hello-nix

# 執行 build 出來的 binary
./result/bin/hello-nix --version

# 使用 nix-shell 進行互動測試
nix-shell -p '(import ./. {}).hello-nix'

確認以下幾件事:

  • ✅ Build 能成功完成
  • ✅ Binary 可以正常執行
  • --help--version 輸出正確
  • ✅ 核心功能運作正常

執行 Nixpkgs 的 Review Checks

Nixpkgs 有自己的 linting 工具,提交前務必跑一次:

# 檢查 package 的 meta 資訊是否完整
nix-env -f . -qaP -A hello-nix --meta --json | python3 -m json.tool

# 使用 nixpkgs-review 測試(推薦)
nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD"

提交 PR 的規範與注意事項

Nixpkgs 的 PR 有一套嚴格的規範,遵循這些規範可以大幅提高你的 PR 被 merge 的速度。

Commit Message 格式

hello-nix: init at 1.2.3

A friendly CLI tool built with Rust.

https://github.com/example-org/hello-nix

格式重點:

  • 第一行<package-name>: init at <version>(新增 package)或 <package-name>: <version-old> -> <version-new>(更新 package)
  • 第二行:空行
  • 第三行起:簡短描述與上游連結

PR Description 要包含什麼

Nixpkgs 的 PR template 會引導你填寫以下資訊:

  1. 動機:為什麼要加入這個 package?
  2. 測試方式:你如何測試過這個 package?
  3. Checklist

將自己加入 Maintainers 列表

如果你是第一次貢獻,需要把自己加入 maintainers/maintainer-list.nix

# maintainers/maintainer-list.nix 中找到合適的字母排序位置
your-github-username = {
  email = "you@example.com";
  github = "your-github-username";
  githubId = 12345678;  # 你的 GitHub user ID
  name = "Your Name";
};

取得 GitHub user ID 的方式:

curl -s https://api.github.com/users/<your-github-username> | jq '.id'

常見的 Review 回饋

根據社群經驗,以下是 reviewer 最常指出的問題:

  1. Hash 不正確:確保所有 hash 都是實際 build 產生的,不要手動編造。
  2. 缺少 meta.mainProgram:如果 package 產出一個可執行檔,務必指定。
  3. License 不正確:仔細確認上游的 license,不要猜。
  4. 描述太簡短或太冗長:一句話簡潔描述即可。
  5. 不必要的 dependencies:只引入 build 和 runtime 真正需要的套件。

替代路線:把你的工作流程徹底自動化

如果你暫時找不到想打包的軟體,另一個值得做的畢業作業是:用 Nix 把你的整個工作環境自動化

目標:一條指令,從裸機到完整開發環境

# flake.nix — 你的個人 dotfiles repo
{
  description = "My complete development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    # macOS 使用者
    darwin = {
      url = "github:LnL7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, home-manager, darwin, ... }: {
    # NixOS 主機配置
    nixosConfigurations.workstation = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./hosts/workstation/configuration.nix
        home-manager.nixosModules.home-manager
        {
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.users.james = import ./home/james.nix;
        }
      ];
    };

    # macOS 配置(透過 nix-darwin)
    darwinConfigurations.macbook = darwin.lib.darwinSystem {
      system = "aarch64-darwin";
      modules = [
        ./hosts/macbook/configuration.nix
        home-manager.darwinModules.home-manager
        {
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.users.james = import ./home/james.nix;
        }
      ];
    };
  };
}

共用的 Home Manager 配置

# home/james.nix
{ pkgs, ... }:

{
  home.stateVersion = "24.05";

  home.packages = with pkgs; [
    # 開發工具
    git
    gh
    neovim
    ripgrep
    fd
    jq
    yq-go
    direnv
    nix-direnv

    # 容器與 Infrastructure
    docker-compose
    kubectl
    terraform
    ansible

    # 語言工具鏈
    rustup
    go
    nodejs_22
    python3
  ];

  programs.git = {
    enable = true;
    userName = "James Hsueh";
    userEmail = "james@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      pull.rebase = true;
      push.autoSetupRemote = true;
    };
    delta.enable = true;
  };

  programs.starship = {
    enable = true;
    settings = {
      add_newline = false;
      character.success_symbol = "[➜](bold green)";
    };
  };

  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };

  programs.zsh = {
    enable = true;
    autosuggestion.enable = true;
    syntaxHighlighting.enable = true;
    shellAliases = {
      ll = "ls -la";
      gs = "git status";
      gc = "git commit";
      nrs = "sudo nixos-rebuild switch --flake .";
    };
  };
}

專案級的 devShell

# 某個專案的 flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { nixpkgs, ... }:
  let
    forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-darwin" ];
  in {
    devShells = forAllSystems (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        default = pkgs.mkShell {
          packages = with pkgs; [
            nodejs_22
            pnpm
            postgresql_16
            redis
          ];

          shellHook = ''
            echo "🚀 開發環境已就緒!"
            echo "Node.js: $(node --version)"
            echo "pnpm: $(pnpm --version)"
          '';

          env = {
            DATABASE_URL = "postgresql://localhost:5432/myapp_dev";
            REDIS_URL = "redis://localhost:6379";
          };
        };
      }
    );
  };
}

搭配 direnv,進入專案目錄就會自動載入開發環境,離開就自動卸載,整個流程無縫銜接。


30 天學習回顧與成長軌跡

讓我們回顧一下這 30 天走過的路:

第一階段:基礎與生存守則(Day 1 – Day 7)

Day主題你學會了…
1觀念重塑Nix Store、Declarative vs Imperative、Immutability
2基礎配置configuration.nix 的編輯與 nixos-rebuild
3套件管理安裝、移除、搜尋套件
4使用者與服務使用者管理、systemd services
5Nix 語言入門基本語法、函式、attribute sets
6Derivation理解 Nix 的 build 基礎單元
7階段複習動手做一個完整的系統配置

第二階段:Flakes 與開發環境(Day 8 – Day 14)

Day主題你學會了…
8Flakes 入門flake.nix、inputs/outputs
9開發環境devShellnix develop
10Overlay覆蓋與擴充 nixpkgs
11Home Manager管理使用者級的 dotfiles
12多機器管理模組化你的配置
13Nix Language 進階let...ininheritwith
14階段專案完整的 Flakes 開發環境

第三階段:系統管理與實務(Day 15 – Day 21)

Day主題你學會了…
15系統服務深入自訂 systemd services
16網路配置Firewall、networking
17ContainerNixOS containers 與 OCI
18磁碟與檔案系統Declarative disk management
19安全性Hardening、secrets management
20除錯技巧常見問題排解
21階段專案完整的 server 配置

第四階段:工程師進階實務(Day 22 – Day 30)

Day主題你學會了…
22Remote Deploymentdeploy-rs、colmena
23CI/CDGitHub Actions + Nix
24Binary CacheCachix、自建 cache
25NixOS Module System深入 module 機制
26Cross Compilation跨平台編譯
27TestingNixOS VM test framework
28Nix DarwinmacOS 上的 Nix 生態
29大型專案實務Monorepo、workspace 管理
30畢業考為 Nixpkgs 貢獻 package 🎓

NixOS 進階學習資源

畢業不代表學習結束。以下是值得持續關注的資源:

官方資源

社群資源

進階主題

  • nix-community:社群維護的各種工具和 modules。
  • Tvix:用 Rust 重新實作的 Nix evaluator,值得關注。
  • Lix:Nix 的社群 fork,在效能和 UX 上做了不少改進。
  • FlakeHub:Flakes 的 registry 與探索平台。

推薦追蹤的人物與部落格

追蹤這些人的 blog 或社群帳號,能讓你持續吸收 Nix 生態系的最新動態:

  • Eelco Dolstra — Nix 的創造者
  • Graham Christensen — Determinate Systems 創辦人
  • Mitchell Hashimoto — 知名的 NixOS 使用者,有很多實務分享

建議的下一步

完成這 30 天之後,你可以朝以下方向繼續精進:

1. 維護你貢獻的 Package

一旦你的 PR 被 merge,你就成為了這個 package 的 maintainer。這代表:

  • 上游發佈新版本時,你需要更新 nixpkgs 裡對應的 package。
  • 如果有人開了 issue 或 PR 涉及你的 package,GitHub 會通知你。
  • 這是一個持續貢獻的好起點。
# 更新 package 版本的 commit message 格式
# hello-nix: 1.2.3 -> 1.3.0

2. 建立你的 NixOS 配置 Repository

把你的完整系統配置放在 Git repository 裡,包含:

my-nix-config/
├── flake.nix            # 入口
├── flake.lock           # 鎖定版本
├── hosts/
│   ├── workstation/     # 工作機配置
│   ├── server/          # 伺服器配置
│   └── macbook/         # macOS 配置
├── home/                # Home Manager 配置
│   ├── common.nix       # 共用設定
│   ├── dev-tools.nix    # 開發工具
│   └── shell.nix        # Shell 配置
├── modules/             # 自訂 modules
│   ├── services/
│   └── programs/
├── overlays/            # 自訂 overlays
└── pkgs/                # 自訂 packages

3. 探索更多 Nix 生態系工具

工具用途
devenv基於 Nix 的開發環境管理(更友善的 API)
nixos-anywhere遠端安裝 NixOS 到任意機器
diskoDeclarative disk partitioning
sops-nixSecrets management for NixOS
impermanence讓 NixOS 在每次重開機時回到乾淨狀態
stylix統一管理全系統的 theme 與配色

4. 加入台灣的 Nix 社群

台灣有一群熱情的 NixOS 使用者,可以在以下地方找到他們:

  • TelegramMatrix 上的台灣 NixOS 群組
  • COSCUPMOPCON 等技術研討會的 Nix 相關議程
  • 在地 meetup 與線上讀書會

和社群互動是加速學習最有效的方式,不要害怕提問。


結語:恭喜畢業!🎓

30 天前,你可能只是聽說 NixOS 很酷、很潮,但完全不知道從何下手。

現在,你已經能夠:

  • ✅ 用 declarative 的方式管理整台機器
  • ✅ 用 Flakes 建立 reproducible 的開發環境
  • ✅ 用 Home Manager 管理你的 dotfiles
  • ✅ 用 NixOS module system 寫出可組合、可重用的配置
  • ✅ 佈署遠端伺服器、設定 CI/CD pipeline
  • ✅ 為 Nixpkgs 貢獻 package

這些不只是知識,更是一套思維方式的轉變。Declarative、Reproducible、Immutable — 這三個詞從此不再只是 buzzword,而是你每天工作的日常。

NixOS 的學習曲線確實陡峭,但你已經翻過了最難的那座山。接下來的路,是越走越順的下坡與延伸。

最後,容我引用 Nix 社群裡常說的一句話:

“It works on my machine."
"Then we’ll ship your machine.”
— 這就是 Nix 的精神。

感謝你走完這 30 天。去吧,用 Nix 把世界變得更 reproducible。🚀


— NixOS 30 天學習之旅・完結 —