成在人线AV无码免费看_18禁网站有哪些_欧美黑人大尺度又粗又长_久久夜色撩人精品国产小说_无码高潮少妇毛多水多水免费

天天快消息!NixOS 與 Nix Flakes 新手入門
時(shí)間:2023-06-07 13:53:19    來源:博客園

獨(dú)立博客閱讀: https://thiscute.world/posts/nixos-and-flake-basics/

長(zhǎng)文警告??


(資料圖)

本文的目標(biāo) NixOS 版本為 22.11,Nix 版本為 2.13.3,在此環(huán)境下 Nix Flakes 仍然為實(shí)驗(yàn)性功能。

目錄零、為什么選擇 Nix一、Nix 簡(jiǎn)介Nix 的優(yōu)點(diǎn)Nix 的缺點(diǎn)簡(jiǎn)單總結(jié)下二、安裝三、Nix Flakes 與舊的 Nix四、NixOS 的 Flakes 包倉(cāng)庫(kù)五、Nix 語言基礎(chǔ)1. 基礎(chǔ)數(shù)據(jù)類型一覽2. let ... in ...3. attribute set 說明4. with 語句5. 繼承 inherit ...6. ${ ... } 字符串插值7. 文件系統(tǒng)路徑8. 搜索路徑9. 多行字符串10. 函數(shù)內(nèi)置函數(shù)import 表達(dá)式pkgs.lib 函數(shù)包11. 不純(Impurities)12. Fetchers13. Derivations六、以聲明式的方式管理系統(tǒng)1. 使用 /etc/nixos/configuration.nix配置系統(tǒng)2. 啟用 NixOS 的 Flakes 支持3. 將系統(tǒng)配置切換到 flake.nix4. 通過 Flakes 來管理系統(tǒng)軟件5. 為 Flake 添加國(guó)內(nèi) cache 源6. 安裝 home-manager7. 模塊化 NixOS 配置8. 更新系統(tǒng)9. 回退個(gè)別軟件包的版本10. 使用 Git 管理 NixOS 配置11. 查看與清理歷史數(shù)據(jù)七、Nix Flakes 的使用1. Flake 的 inputs2. Flake 的 outputs3. Flake 命令行的使用八、Nixpkgs 的高級(jí)用法OverridingOverlays模塊化 overlays 配置進(jìn)階玩法總結(jié)參考零、為什么選擇 Nix

好幾年前就聽說過 Nix 包管理器,它用 Nix 語言編寫配置來管理系統(tǒng)依賴,此外基于 Nix 包管理器設(shè)計(jì)的 Linux 發(fā)行版 NixOS,還能隨時(shí)回滾到任一歷史狀態(tài)。雖然聽著很牛,但是不僅要多學(xué)一門語言,裝個(gè)包還得寫代碼,當(dāng)時(shí)覺得太麻煩就沒研究。但是最近搞系統(tǒng)遷移遇到兩件麻煩事,使我決定嘗試下 Nix.

第一件事是在新組裝的 PC 主機(jī)上安裝 EndeavourOS(Arch Linux 的一個(gè)衍生發(fā)行版),因?yàn)榕f系統(tǒng)也是 EndeavourOS 系統(tǒng),安裝完為了省事,我就直接把舊電腦的 Home 目錄 rsync 同步到了新 PC 上。這一同步就出了問題,所有功能都工作正常,但是視頻播放老是卡住,firefox/chrome/mpv 都會(huì)卡住,網(wǎng)上找各種資料都沒解決,還是我靈光一閃想到是不是 Home 目錄同步的鍋,清空了 Home 目錄,問題立馬就解決了...后面又花好長(zhǎng)時(shí)間從舊電腦一點(diǎn)點(diǎn)恢復(fù) Home 目錄下的東西。

第二件事是,我最近想嘗鮮 wayland,把桌面從 i3wm 換成了 sway,但是因?yàn)橛闷饋韰^(qū)別不明顯,再加上諸多不便(hidpi、sway 配置調(diào)優(yōu)都要花時(shí)間精力),嫌麻煩就還是回退到了 i3wm。結(jié)果回退后,每次系統(tǒng)剛啟動(dòng)時(shí),有一段時(shí)間 firefox/thunar 等 GUI 程序會(huì)一直卡著,要大概 1 分鐘后才能正常啟動(dòng)...

發(fā)生第二件事時(shí)我就懶得折騰了,想到歸根結(jié)底還是系統(tǒng)沒有版本控制跟回滾機(jī)制,導(dǎo)致出了問題不能還原,裝新系統(tǒng)時(shí)各種軟件包也全靠自己手工從舊機(jī)器導(dǎo)出軟件包清單,再在新機(jī)器安裝恢復(fù)。就打算干脆換成 NixOS.

我折騰的第一步是在我 Homelab 上開了臺(tái) NixOS 虛擬機(jī),在這臺(tái)虛擬機(jī)里一步步調(diào)試,把我物理機(jī)的 EndeavourOS i3 配置遷移到 NixOS + Flakes,還原出了整個(gè)桌面環(huán)境。在虛擬機(jī)里搞定后問題就不大了,直接備份好我辦公電腦的 Home 目錄、軟件清單,然后將系統(tǒng)重裝為 NixOS,再 git clone 我調(diào)試好的 NixOS 配置,改一改硬盤掛載相關(guān)的參數(shù),額外補(bǔ)充下 Nvidia 顯卡相關(guān)的 NixOS 配置,最后一行命令部署配置。幾行命令就在我全新的 NixOS 系統(tǒng)上還原出了整個(gè) i3 桌面環(huán)境跟我的常用軟件,那一刻真的很有成就感!

NixOS 的回滾能力給了我非常大的底氣——再也不怕把系統(tǒng)搞掛了,于是我前幾天我又進(jìn)一步遷移到了 hyprland 桌面,確實(shí)比 i3 香多了,它的動(dòng)畫效果我吹爆?。ㄔ谝郧?EndeavourOS 上我肯定是不太敢做這樣的切換的,原因前面已經(jīng)解釋過了——萬一把系統(tǒng)搞出問題,會(huì)非常麻煩。)

補(bǔ)充:v2ex 上有 v 友反饋 btrfs 文件系統(tǒng)的快照功能,也能提供類似的回滾能力,而且簡(jiǎn)單很多。我研究了下發(fā)現(xiàn)確實(shí)如此,btrfs 甚至也可以像 NixOS 一樣配置 grub 從快照啟動(dòng)。所以如果你只是想要系統(tǒng)回滾能力,那么基于 btrfs 快照功能的 btrbk 也是一個(gè)不錯(cuò)的選擇。當(dāng)然如果你仍然對(duì) Nix 感興趣,那學(xué)一學(xué)也絕對(duì)不虧,畢竟 Nix 的能力遠(yuǎn)不止于此,系統(tǒng)快照只是它能力的一部分而已~

在學(xué)了大半個(gè)月的 NixOS 與 Nix Flakes 后,我終于將我的 PC 從 EndeavouOS 系統(tǒng)切換到了 NixOS,這篇文章就脫胎于我這段時(shí)間的折騰筆記,希望能對(duì)你有所幫助~

前因后果交代完畢,那么下面開始正文!

一、Nix 簡(jiǎn)介

Nix 包管理器,跟 DevOps 領(lǐng)域當(dāng)前流行的 pulumi/terraform/kubernetes 類似,都是聲明式配置管理工具,用戶需要在某些配置文件中聲明好期望的系統(tǒng)狀態(tài),而 Nix 負(fù)責(zé)達(dá)成目標(biāo)。區(qū)別在于 Nix 的管理目標(biāo)是軟件包,而 pulumi/terraform 的管理目標(biāo)是云上資源。

簡(jiǎn)單解釋下什么是「聲明式配置」,它是指用戶只需要聲明好自己想要的結(jié)果——比如說希望將 i3 桌面替換成 sway 桌面,Nix 就會(huì)幫用戶達(dá)成這個(gè)目標(biāo)。用戶不需要關(guān)心底層細(xì)節(jié)(比如說 sway 需要安裝哪些軟件包,哪些 i3 相關(guān)的軟件包需要卸載掉,哪些系統(tǒng)配置或環(huán)境變量需要針對(duì) sway 做調(diào)整、如果使用了 Nvidia 顯卡 Sway 參數(shù)要做什么調(diào)整才能正常運(yùn)行等等),Nix 會(huì)自動(dòng)幫用戶處理這些細(xì)節(jié)。

基于 Nix 構(gòu)建的 Linux 發(fā)行版 NixOS,可以簡(jiǎn)單用 OS as Code 來形容,它通過聲明式的 Nix 配置文件來描述整個(gè)操作系統(tǒng)的狀態(tài)。

NixOS 的配置只負(fù)責(zé)管理系統(tǒng)層面的狀態(tài),用戶目錄不受它管轄。有另一個(gè)重要的社區(qū)項(xiàng)目 home-manager 專門用于管理用戶目錄,將 home-manager 與 NixOS、Git 結(jié)合使用,就可以得到一個(gè)完全可復(fù)現(xiàn)、可回滾的系統(tǒng)環(huán)境。

因?yàn)?Nix 聲明式、可復(fù)現(xiàn)的特性,Nix 不僅可用于管理桌面電腦的環(huán)境,也有很多人用它管理開發(fā)編譯環(huán)境、云上虛擬機(jī)、容器鏡像構(gòu)建,Nix 官方的 NixOps 與社區(qū)的 deploy-rs 都是基于 Nix 實(shí)現(xiàn)的運(yùn)維工具。

Home 目錄下文件眾多,行為也不一,因此不可能對(duì)其中的所有文件進(jìn)行版本控制,代價(jià)太高。一般僅使用 home-manager 管理一些重要的配置文件,而其他需要備份的文件可以用 rsync/synthing 等手段做備份同步,或者用 btrbk 之類的工具對(duì) home 目錄做快照。

Nix 的優(yōu)點(diǎn)聲明式配置,Environment as Code,可以直接用 Git 管理配置,只要配置文件不丟,系統(tǒng)就可以隨時(shí)還原到任一歷史狀態(tài)(理想情況下)。這跟一些編程語言中 cargo.lock/go.mod 等文件鎖定依賴庫(kù)版本以確保構(gòu)建結(jié)果可復(fù)現(xiàn)的思路是一致的。與 Docker 相比,Dockerfile 實(shí)際是命令式的配置,而且也不存在版本鎖這樣的東西,所以 Docker 的可復(fù)現(xiàn)能力遠(yuǎn)不如 Nix.高度便捷的系統(tǒng)自定義能力通過改幾行配置,就可以簡(jiǎn)單地更換系統(tǒng)的各種組件。這是因?yàn)?Nix 將底層的復(fù)雜操作全部封裝在了 nix package 包中,只給用戶提供了簡(jiǎn)潔且必要的聲明式參數(shù)。而且這種修改非常安全,例證之一是有 v 友表示「nixos 切換不同桌面非常簡(jiǎn)單干凈,而且很安全,我就經(jīng)常 gnome/kde/sway 來回?fù)Q著用?!箍苫貪L:可以隨時(shí)回滾到任一歷史環(huán)境,NixOS 甚至默認(rèn)將所有舊版本都加入到了啟動(dòng)項(xiàng),確保系統(tǒng)滾掛了也能隨時(shí)回退。所以 Nix 也被認(rèn)為是最穩(wěn)定的包管理方式。沒有依賴沖突問題:因?yàn)?Nix 中每個(gè)軟件包都擁有唯一的 hash,其安裝路徑中也會(huì)包含這個(gè) hash 值,因此可以多版本共存。社區(qū)很活躍,第三方項(xiàng)目也挺豐富,官方包倉(cāng)庫(kù) nixpkgs 貢獻(xiàn)者眾多,也有很多人分享自己的 Nix 配置,一遍瀏覽下來,整個(gè)生態(tài)給我一種發(fā)現(xiàn)新大陸的興奮感。Nix 的缺點(diǎn)學(xué)習(xí)成本高:如果你希望系統(tǒng)完全可復(fù)現(xiàn),并且避免各種不當(dāng)使用導(dǎo)致的坑,那就需要學(xué)習(xí)了解 Nix 的整個(gè)設(shè)計(jì),并以聲明式的方式管理系統(tǒng),不能無腦 nix-env -i(這類似 apt-get install)。文檔混亂:首先 Nix Flakes 目前仍然是實(shí)驗(yàn)性特性,介紹它本身的文檔目前比較匱乏。 其次 Nix 社區(qū)絕大多數(shù)文檔都只介紹了舊的 nix-env/nix-channel,想直接從 Nix Flakes 開始學(xué)習(xí)的話,需要參考大量舊文檔,從中提取出自己需要的內(nèi)容。另外一些 Nix 當(dāng)前的核心功能,官方文檔都語焉不詳(比如 imports跟 Nixpkgs Module System),想搞明白基本只能看源碼了...包數(shù)量比較少:撤回下這一條,官方宣稱 nixpkgs 是有 80000+ 個(gè)軟件包,使用下來確實(shí)絕大部分包都能在 nixpkgs 里找到,體驗(yàn)還是不錯(cuò)滴。比較吃硬盤空間:為了保證系統(tǒng)可以隨時(shí)回退,nix 默認(rèn)總是保留所有歷史環(huán)境,這非常吃硬盤空間。雖然可以定期使用 nix-collect-garbage來手動(dòng)清理舊的歷史環(huán)境,也還是建議配置個(gè)更大的硬盤...報(bào)錯(cuò)信息比較隱晦:一般的報(bào)錯(cuò)提示還是比較清楚的,但是遇到好幾次依賴版本有問題或者傳參錯(cuò)誤提示不出原因,--show-trace直接輸出一堆的內(nèi)部堆棧,都花了很長(zhǎng)時(shí)間才定位到,通過升級(jí)依賴版本或者修正參數(shù)后問題解決。猜測(cè)導(dǎo)致這個(gè)問題的原因有兩個(gè),一是 Nix 是動(dòng)態(tài)語言,各種參數(shù)都是運(yùn)行時(shí)才確定類型。二是我用到的 flake 包的錯(cuò)誤處理邏輯寫得不太好,錯(cuò)誤提示不清晰,一些隱晦的錯(cuò)誤甚至通過錯(cuò)誤堆棧也定位不到原因。簡(jiǎn)單總結(jié)下

總的來說,我覺得 NixOS 適合那些有一定 Linux 使用經(jīng)驗(yàn)與編程經(jīng)驗(yàn),并且希望對(duì)自己的系統(tǒng)擁有更強(qiáng)掌控力的開發(fā)者。

另外一條信息:在開發(fā)環(huán)境搭建方面 Nix 與相對(duì)流行的 Dev Containers 也有些競(jìng)爭(zhēng)關(guān)系,它們的具體區(qū)別還有待我發(fā)掘~

二、安裝

Nix 有多種安裝方式,支持以包管理器的形式安裝到 MacOS/Linux/WSL 三種系統(tǒng)上,Nix 還額外提供了 NixOS ——一個(gè)使用 Nix 管理整個(gè)系統(tǒng)環(huán)境的 Linux 發(fā)行版。

我選擇了直接使用 NixOS 的 ISO 鏡像安裝 NixOS 系統(tǒng),從而最大程度上通過 Nix 管理整個(gè)系統(tǒng)的環(huán)境。

安裝很簡(jiǎn)單,這里不多介紹,僅列一下我覺得比較有用的參考資料:

國(guó)內(nèi)鏡像源說明:https://mirrors.bfsu.edu.cn/help/nix/Nix 的官方安裝方式: 使用 bash 腳本編寫, 目前(2023-04-23)為止 nix-command& flakes仍然是實(shí)驗(yàn)性特性,需要手動(dòng)開啟。你需要參照 Enable flakes - NixOS Wiki 的說明啟用 nix-command& flakes官方不提供任何卸載手段,要在 Linux/MacOS 上卸載 Nix,你需要手動(dòng)刪除所有相關(guān)的文件、用戶以及用戶組The Determinate Nix Installer: 第三方使用 Rust 編寫的 installer, 默認(rèn)啟用 nix-command& flakes,并且提供了卸載命令。三、Nix Flakes 與舊的 Nix

Nix 于 2020 年推出了 nix-command& flakes兩個(gè)新特性,它們提供了全新的命令行工具、標(biāo)準(zhǔn)的 Nix 包結(jié)構(gòu)定義、類似 cargo/npm 的 flake.lock版本鎖文件等等。這兩個(gè)特性極大地增強(qiáng)了 Nix 的能力,因此雖然至今(2023/5/5)它們?nèi)匀皇菍?shí)驗(yàn)性特性,但是已經(jīng)被 Nix 社區(qū)廣泛使用,是強(qiáng)烈推薦使用的功能。

目前 Nix 社區(qū)的絕大多數(shù)文檔仍然只介紹了傳統(tǒng) Nix,不包含 Nix Flakes 相關(guān)的內(nèi)容,但是從可復(fù)現(xiàn)、易于管理維護(hù)的角度講,舊的 Nix 包結(jié)構(gòu)與命令行工具已經(jīng)不推薦使用了,因此本文檔也不會(huì)介紹舊的 Nix 包結(jié)構(gòu)與命令行工具的使用方法,也建議新手直接忽略掉這些舊的內(nèi)容,從 nix-command& flakes學(xué)起。

這里列舉下在 nix-command& flakes中已經(jīng)不需要用到的舊的 Nix 命令行工具與相關(guān)概念,在查找資料時(shí),如果看到它們直接忽略掉就行:

nix-channel: nix-channel 與 apt/yum/pacman 等其他 Linux 發(fā)行版的包管理工具類似,通過 stable/unstable/test 等 channel 來管理軟件包的版本。Nix Flakes 在 flake.nix 中通過 inputs 聲明依賴包的數(shù)據(jù)源,通過 flake.lock 鎖定依賴版本,完全取代掉了 nix-channel 的功能。nix-env: 用于管理用戶環(huán)境的軟件包,是傳統(tǒng) Nix 的核心命令行工具。它從 nix-channel 定義的數(shù)據(jù)源中安裝軟件包,所以安裝的軟件包版本受 channel 影響。通過 nix-env安裝的包不會(huì)被自動(dòng)記錄到 Nix 的聲明式配置中,是完全脫離掌控的,無法在其他主機(jī)上復(fù)現(xiàn),因此不推薦使用。在 Nix Flakes 中對(duì)應(yīng)的命令為 nix profilenix-shell: nix-shell 用于創(chuàng)建一個(gè)臨時(shí)的 shell 環(huán)境在 Nix Flakes 中它被 nix developnix shell取代了。nix-build: 用于構(gòu)建 Nix 包,它會(huì)將構(gòu)建結(jié)果放到 /nix/store路徑下,但是不會(huì)記錄到 Nix 的聲明式配置中。在 Nix Flakes 中對(duì)應(yīng)的命令為 nix build...四、NixOS 的 Flakes 包倉(cāng)庫(kù)

跟 Arch Linux 類似,Nix 也有官方與社區(qū)的軟件包倉(cāng)庫(kù):

nixpkgs 是一個(gè)包含了所有 Nix 包與 NixOS 模塊/配置的 Git 倉(cāng)庫(kù),其 master 分支包含最新的 Nix 包與 NixOS 模塊/配置。比如 qq 就直接包含在 nixpkgs 中了NUR: 類似 Arch Linux 的 AUR,NUR 是 Nix 的一個(gè)第三方的 Nix 包倉(cāng)庫(kù),算是 nixpkgs 的一個(gè)增補(bǔ)包倉(cāng)庫(kù)。這些常用國(guó)產(chǎn)軟件,都可以通過 NUR 安裝:qqmusicwechat-uosdingtalk更多程序,可以在這里搜索:Nix User RepositoriesNix Flakes 也可直接從 Git 倉(cāng)庫(kù)中安裝軟件包,這種方式可以用于安裝任何人提供的 Flakes 包

此外一些沒有 Nix 支持或者支持不佳的軟件,也可以考慮通過 Flatpak 或者 AppImage 的方式安裝使用,這兩個(gè)都是在所有 Linux 發(fā)行版上可用的軟件打包與安裝手段,詳情請(qǐng)自行搜索,這里就不介紹細(xì)節(jié)了。

五、Nix 語言基礎(chǔ)

https://nix.dev/tutorials/first-steps/nix-language

Nix 語言是 Nix 的基礎(chǔ),要想玩得轉(zhuǎn) NixOS 與 Nix Flakes,享受到它們帶來的諸多好處,就必須學(xué)會(huì)這門語言。

Nix 是一門比較簡(jiǎn)單的函數(shù)式語言,在已有一定編程基礎(chǔ)的情況下,過一遍這些語法用時(shí)應(yīng)該在 2 個(gè)小時(shí)以內(nèi),本文假設(shè)你具有一定編程基礎(chǔ)(也就是說寫得不會(huì)很細(xì))。

這一節(jié)主要包含如下內(nèi)容:

數(shù)據(jù)類型let...in... with inherit 等特殊語法函數(shù)的聲明與調(diào)用語法內(nèi)置函數(shù)與庫(kù)函數(shù)inputs 的不純性(Impurities)用于描述 Build Task 的 DerivationOverriding 與 Overlays...

先把語法過一遍,有個(gè)大概的印象就行,后面需要用到時(shí)再根據(jù)右側(cè)目錄回來復(fù)習(xí)。

1. 基礎(chǔ)數(shù)據(jù)類型一覽

下面通過一個(gè) attribute set (這類似 json 或者其他語言中的 map/dict)來簡(jiǎn)要說明所有基礎(chǔ)數(shù)據(jù)類型:

{  string = "hello";  integer = 1;  float = 3.141;  bool = true;  null = null;  list = [ 1 "two" false ];  attribute-set = {    a = "hello";    b = 2;    c = 2.718;    d = false;  }; # comments are supported}

以及一些基礎(chǔ)操作符(普通的算術(shù)運(yùn)算、布爾運(yùn)算就跳過不介紹了):

# 列表拼接[ 1 2 3 ] ++ [ 4 5 6 ] # [ 1 2 3 4 5 6 ]# 將 // 后面的 attribut set 中的內(nèi)容,全部更新到 // 前面的 attribute set 中{ a = 1; b = 2; } // { b = 3; c = 4; } # 結(jié)果為 { a = 1; b = 3; c = 4; }# 邏輯隱含,等同于 !b1 || b2.bool -> bool
2. let ... in ...

Nix 的 let ... in ...語法被稱作「let 表達(dá)式」或者「let 綁定」,它用于創(chuàng)建臨時(shí)使用的局部變量:

let  a = 1;ina + a  # 結(jié)果是 2

let 表達(dá)式中的變量只能在 in之后的表達(dá)式中使用,理解成臨時(shí)變量就行。

3. attribute set 說明

花括號(hào) {}用于創(chuàng)建 attribute set,也就是 key-value 對(duì)的集合,類似于 JSON 中的對(duì)象。

attribute set 默認(rèn)不支持遞歸引用,如下內(nèi)容會(huì)報(bào)錯(cuò):

{  a = 1;  b = a + 1; # error: undefined variable "a"}

不過 Nix 提供了 rec關(guān)鍵字(recursive attribute set),可用于創(chuàng)建遞歸引用的 attribute set:

rec {  a = 1;  b = a + 1; # ok}

在遞歸引用的情況下,Nix 會(huì)按照聲明的順序進(jìn)行求值,所以如果 ab之后聲明,那么 b會(huì)報(bào)錯(cuò)。

可以使用 .操作符來訪問 attribute set 的成員:

let  a = {    b = {      c = 1;    };  };ina.b.c # 結(jié)果是 1

.操作符也可直接用于賦值:

{ a.b.c = 1; }

此外 attribute set 還支持一個(gè) has attribute 操作符,它可用于檢測(cè) attribute set 中是否包含某個(gè)屬性,返回 bool 值:

let  a = {    b = {      c = 1;    };  };ina?b  # 結(jié)果是 true,因?yàn)?a.b 這個(gè)屬性確實(shí)存在

has attribute 操作符在 nixpkgs 庫(kù)中常被用于檢測(cè)處理 args?system等參數(shù),以 (args?system)(! args?system)的形式作為函數(shù)參數(shù)使用(嘆號(hào)表示對(duì) bool 值取反,是常見 bool 值運(yùn)算符)。

4. with 語句

with 語句的語法如下:

with  ; 

with語句會(huì)將 中的所有成員添加到當(dāng)前作用域中,這樣在 中就可以直接使用 中的成員了,簡(jiǎn)化 attribute set 的訪問語法,比如:

let  a = {    x = 1;    y = 2;    z = 3;  };inwith a; [ x y z ]  # 結(jié)果是 [ 1 2 3 ], 等價(jià)于 [ a.x a.y a.z ]
5. 繼承 inherit ...

inherit語句用于從 attribute set 中繼承成員,同樣是一個(gè)簡(jiǎn)化代碼的語法糖,比如:

let  x = 1;  y = 2;in{  inherit x y;}  # 結(jié)果是 { x = 1; y = 2; }

inherit 還能直接從某個(gè) attribute set 中繼承成員,語法為 inherit () ;,比如:

let  a = {    x = 1;    y = 2;    z = 3;  };in{  inherit (a) x y;}  # 結(jié)果是 { x = 1; y = 2; }
6. ${ ... } 字符串插值

${ ... }用于字符串插值,懂點(diǎn)編程的應(yīng)該都很容易理解這個(gè),比如:

let  a = 1;in"the value of a is ${a}"  # 結(jié)果是 "the value of a is 1"
7. 文件系統(tǒng)路徑

Nix 中不帶引號(hào)的字符串會(huì)被解析為文件系統(tǒng)路徑,路徑的語法與 Unix 系統(tǒng)相同。

8. 搜索路徑

請(qǐng)不要使用這個(gè)功能,它會(huì)導(dǎo)致不可預(yù)期的行為。

Nix 會(huì)在看到 這類三角括號(hào)語法時(shí),會(huì)在 NIX_PATH環(huán)境變量中指定的路徑中搜索該路徑。

因?yàn)榄h(huán)境變量 NIX_PATH是可變更的值,所以這個(gè)功能是不純的,會(huì)導(dǎo)致不可預(yù)期的行為。

在這里做個(gè)介紹,只是為了讓你在看到別人使用類似的語法時(shí)不至于抓瞎。

9. 多行字符串

多行字符串的語法為 "",比如:

""  this is a  multi-line  string""
10. 函數(shù)

函數(shù)的聲明語法為:

:  

舉幾個(gè)常見的例子:

# 單參數(shù)函數(shù)a: a + a# 嵌套函數(shù)a: b: a + b# 雙參數(shù)函數(shù){ a, b }: a + b# 雙參數(shù)函數(shù),帶默認(rèn)值。問號(hào)后面的是參數(shù)的默認(rèn)值{ a ? 1, b ? 2 }: a + b# 帶有命名 attribute set 作為參數(shù)的函數(shù),并且使用 ... 收集其他可選參數(shù)# 命名 args 與 ... 可選參數(shù)通常被一起作為函數(shù)的參數(shù)定義使用args@{ a, b, ... }: a + b + args.c# 如下內(nèi)容等價(jià)于上面的內(nèi)容,{ a, b, ... }@args: a + b + args.c# 但是要注意命名參數(shù)僅綁定了輸入的 attribute set,默認(rèn)參數(shù)不在其中,舉例let  f = { a ? 1, b ? 2, ... }@args: argsin  f {}  # 結(jié)果是 {},也就說明了 args 中包含默認(rèn)值# 函數(shù)的調(diào)用方式就是把參數(shù)放在后面,比如下面的 2 就是前面這個(gè)函數(shù)的參數(shù)a: a + a 2  # 結(jié)果是 4# 還可以給函數(shù)命名,不過必須使用 let 表達(dá)式let  f = a: a + a;in  f 2  # 結(jié)果是 4
內(nèi)置函數(shù)

Nix 內(nèi)置了一些函數(shù),可通過 builtins.來調(diào)用,比如:

builtins.add 1 2  # 結(jié)果是 3

詳細(xì)的內(nèi)置函數(shù)列表參見 Built-in Functions - Nix Reference Mannual

import 表達(dá)式

import表達(dá)式以其他 Nix 文件的路徑作為參數(shù),返回該 Nix 文件的執(zhí)行結(jié)果。

import的參數(shù)如果為文件夾路徑,那么會(huì)返回該文件夾下的 default.nix文件的執(zhí)行結(jié)果。

舉個(gè)例子,首先創(chuàng)建一個(gè) file.nix文件:

$ echo "x: x + 1" > file.nix

然后使用 import 執(zhí)行它:

import ./file.nix 1  # 結(jié)果是 2
pkgs.lib 函數(shù)包

除了 builtins 之外,Nix 的 nixpkgs 倉(cāng)庫(kù)還提供了一個(gè)名為 lib的 attribute set,它包含了一些常用的函數(shù),它通常被以如下的形式被使用:

let  pkgs = import  {};inpkgs.lib.strings.toUpper "search paths considered harmful"  # 結(jié)果是 "SEARCH PATHS CONSIDERED HARMFUL"

可以通過 Nixpkgs Library Functions - Nixpkgs Manual 查看 lib 函數(shù)包的詳細(xì)內(nèi)容。

11. 不純(Impurities)

Nix 語言本身是純函數(shù)式的,是純的,「純」是指它就跟數(shù)學(xué)中的函數(shù)一樣,同樣的輸入永遠(yuǎn)得到同樣的輸出。

Nix 有兩種構(gòu)建輸入,一種是從文件系統(tǒng)路徑等輸入源中讀取文件,另一種是將其他函數(shù)作為輸入。

Nix 唯一的不純之處在這里:從文件系統(tǒng)路徑或者其他輸入源中讀取文件作為構(gòu)建任務(wù)的輸入,這些輸入源參數(shù)可能沒變化,但是文件內(nèi)容或數(shù)據(jù)源的返回內(nèi)容可能會(huì)變化,這就會(huì)導(dǎo)致輸入相同,Nix 函數(shù)的輸出卻可能不同——函數(shù)變得不純了。

Nix 中的搜索路徑與 builtins.currentSystem也是不純的,但是這兩個(gè)功能都不建議使用,所以這里略過了。

12. Fetchers

構(gòu)建輸入除了直接來自文件系統(tǒng)路徑之外,還可以通過 Fetchers 來獲取,F(xiàn)etcher 是一種特殊的函數(shù),它的輸入是一個(gè) attribute set,輸出是 Nix Store 中的一個(gè)系統(tǒng)路徑。

Nix 提供了四個(gè)內(nèi)置的 Fetcher,分別是:

builtins.fetchurl:從 url 中下載文件builtins.fetchTarball:從 url 中下載 tarball 文件builtins.fetchGit:從 git 倉(cāng)庫(kù)中下載文件builtins.fetchClosure:從 Nix Store 中獲取 Derivation

舉例:

builtins.fetchurl "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"# result example => "/nix/store/7dhgs330clj36384akg86140fqkgh8zf-7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"builtins.fetchTarball "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"# result example(auto unzip the tarball) => "/nix/store/d59llm96vgis5fy231x6m7nrijs0ww36-source"
13. Derivations

一個(gè)構(gòu)建動(dòng)作的 Nix 語言描述被稱做一個(gè) Derivation,它描述了如何構(gòu)建一個(gè)軟件包,它的構(gòu)建結(jié)果是一個(gè) Store Object

Store Object 的存放路徑格式為 /nix/store/-,其中 是構(gòu)建結(jié)果的 hash 值,是它的名字。路徑 hash 值確保了每個(gè)構(gòu)建結(jié)果都是唯一的,因此可以多版本共存,而且不會(huì)出現(xiàn)依賴沖突的問題。

/nix/store被稱為 Store,存放所有的 Store Objects,這個(gè)路徑被設(shè)置為只讀,只有 Nix 本身才能修改這個(gè)路徑下的內(nèi)容,以保證系統(tǒng)的可復(fù)現(xiàn)性。

在 Nix 語言的最底層,一個(gè)構(gòu)建任務(wù)就是使用 builtins 中的不純函數(shù) derivation創(chuàng)建的,我們實(shí)際使用的 stdenv.mkDerivation就是它的一個(gè) wrapper,屏蔽了底層的細(xì)節(jié),簡(jiǎn)化了用法。

六、以聲明式的方式管理系統(tǒng)

https://nixos.wiki/wiki/Overview_of_the_NixOS_Linux_distribution

了解了 Nix 語言的基本用法之后,我們就可以開始使用 Nix 語言來配置 NixOS 系統(tǒng)了。NixOS 的系統(tǒng)配置路徑為 /etc/nixos/configuration.nix,它包含系統(tǒng)的所有聲明式配置,如時(shí)區(qū)、語言、鍵盤布局、網(wǎng)絡(luò)、用戶、文件系統(tǒng)、啟動(dòng)項(xiàng)等。

如果想要以可復(fù)現(xiàn)的方式修改系統(tǒng)的狀態(tài)(這也是最推薦的方式),就需要手工修改 /etc/nixos/configuration.nix文件,然后執(zhí)行 sudo nixos-rebuild switch命令來應(yīng)用配置,此命令會(huì)根據(jù)配置文件生成一個(gè)新的系統(tǒng)環(huán)境,并將新的環(huán)境設(shè)為默認(rèn)環(huán)境。同時(shí)上一個(gè)系統(tǒng)環(huán)境會(huì)被保留,而且會(huì)被加入到 grub 的啟動(dòng)項(xiàng)中,這確保了即使新的環(huán)境不能啟動(dòng),也能隨時(shí)回退到舊環(huán)境。

另一方面,/etc/nixos/configuration.nix是傳統(tǒng)的 Nix 配置方式,它依賴 nix-channel配置的數(shù)據(jù)源,也沒有任何版本鎖定機(jī)制,實(shí)際無法確保系統(tǒng)的可復(fù)現(xiàn)性。更推薦使用的是 Nix Flakes,它可以確保系統(tǒng)的可復(fù)現(xiàn)性,同時(shí)也可以很方便地管理系統(tǒng)的配置。

我們下面首先介紹下通過 NixOS 默認(rèn)的配置方式來管理系統(tǒng),然后再過渡到更先進(jìn)的 Nix Flakes.

1. 使用 /etc/nixos/configuration.nix配置系統(tǒng)

前面提過了這是傳統(tǒng)的 Nix 配置方式,也是當(dāng)前 NixOS 默認(rèn)使用的配置方式,它依賴 nix-channel配置的數(shù)據(jù)源,也沒有任何版本鎖定機(jī)制,實(shí)際無法確保系統(tǒng)的可復(fù)現(xiàn)性。

簡(jiǎn)單起見我們先使用這種方式來配置系統(tǒng),后面會(huì)介紹 Flake 的使用。

比如要啟用 ssh 并添加一個(gè)用戶 ryan,只需要在 /etc/nixos/configuration.nix中添加如下配置:

# Edit this configuration file to define what should be installed on# your system.  Help is available in the configuration.nix(5) man page# and in the NixOS manual (accessible by running ‘nixos-help’).{ config, pkgs, ... }:{  imports =    [ # Include the results of the hardware scan.      ./hardware-configuration.nix    ];  # 省略掉前面的配置......  # 新增用戶 ryan  users.users.ryan = {    isNormalUser = true;    description = "ryan";    extraGroups = [ "networkmanager" "wheel" ];    openssh.authorizedKeys.keys = [        # replace with your own public key        "ssh-ed25519  ryan@ryan-pc"    ];    packages = with pkgs; [      firefox    #  thunderbird    ];  };  # 啟用 OpenSSH 后臺(tái)服務(wù)  services.openssh = {    enable = true;    permitRootLogin = "no";         # disable root login    passwordAuthentication = false; # disable password login    openFirewall = true;    forwardX11 = true;              # enable X11 forwarding  };  # 省略其他配置......}

這里我啟用了 openssh 服務(wù),為 ryan 用戶添加了 ssh 公鑰,并禁用了密碼登錄。

現(xiàn)在運(yùn)行 sudo nixos-rebuild switch部署修改后的配置,之后就可以通過 ssh 密鑰遠(yuǎn)程登錄到我的這臺(tái)主機(jī)了。

這就是 NixOS 默認(rèn)的聲明式系統(tǒng)配置,要對(duì)系統(tǒng)做任何可復(fù)現(xiàn)的變更,都只需要修改 /etc/nixos/configuration.nix文件,然后運(yùn)行 sudo nixos-rebuild switch部署變更即可。

/etc/nixos/configuration.nix的所有配置項(xiàng),可以在這幾個(gè)地方查到:

直接 Google,比如 Chrome NixOS就能找到 Chrome 相關(guān)的配置項(xiàng),一般 NixOS Wiki 或 nixpkgs 倉(cāng)庫(kù)源碼的排名會(huì)比較靠前。在 NixOS Options Search 中搜索關(guān)鍵字系統(tǒng)級(jí)別的配置,可以考慮在 Configuration - NixOS Manual 找找相關(guān)文檔直接在 nixpkgs 倉(cāng)庫(kù)中搜索關(guān)鍵字,讀相關(guān)的源碼。2. 啟用 NixOS 的 Flakes 支持

與 NixOS 默認(rèn)的配置方式相比,Nix Flakes 提供了更好的可復(fù)現(xiàn)性,同時(shí)它清晰的包結(jié)構(gòu)定義原生支持了以其他 Git 倉(cāng)庫(kù)為依賴,便于代碼分享,因此更建議使用 Nix Flakes 來管理系統(tǒng)配置。

但是目前 Nix Flakes 作為一個(gè)實(shí)驗(yàn)性的功能,仍未被默認(rèn)啟用。所以我們需要手動(dòng)啟用它,修改 /etc/nixos/configuration.nix文件,在函數(shù)塊中啟用 flakes 與 nix-command 功能:

# Edit this configuration file to define what should be installed on# your system.  Help is available in the configuration.nix(5) man page# and in the NixOS manual (accessible by running ‘nixos-help’).{ config, pkgs, ... }:{  imports =    [ # Include the results of the hardware scan.      ./hardware-configuration.nix    ];  # 省略掉前面的配置......  # 啟用 Nix Flakes 功能,以及配套的新 nix-command 命令行工具  nix.settings.experimental-features = [ "nix-command" "flakes" ];  environment.systemPackages = with pkgs; [    git  # Nix Flakes 通過 git 命令從數(shù)據(jù)源拉取依賴,所以必須先安裝好 git    vim    wget  ];  # 省略其他配置......}

然后運(yùn)行 sudo nixos-rebuild switch應(yīng)用修改后,即可使用 Nix Flakes 來管理系統(tǒng)配置。

額外還有個(gè)好處就是,現(xiàn)在你可以通過 nix repl打開一個(gè) nix 交互式環(huán)境,有興趣的話,可以使用它復(fù)習(xí)測(cè)試一遍前面學(xué)過的所有 Nix 語法。

3. 將系統(tǒng)配置切換到 flake.nix

在啟用了 Nix Flakes 特性后,sudo nixos-rebuild switch命令會(huì)優(yōu)先讀取 /etc/nixos/flake.nix文件,如果找不到再嘗試使用 /etc/nixos/configuration.nix。

可以首先使用官方提供的模板來學(xué)習(xí) flake 的編寫,先查下有哪些模板:

nix flake show templates

其中有個(gè) templates#full模板展示了所有可能的用法,可以看看它的內(nèi)容:

nix flake init -t templates#fullcat flake.nix

我們參照該模板創(chuàng)建文件 /etc/nixos/flake.nix并編寫好配置內(nèi)容,后續(xù)系統(tǒng)的所有修改都將全部由 Nix Flakes 接管,示例內(nèi)容如下:

{  description = "Ryan"s NixOS Flake";  # 這是 flake.nix 的標(biāo)準(zhǔn)格式,inputs 是 flake 的依賴,outputs 是 flake 的輸出  # inputs 中的每一項(xiàng)依賴都會(huì)在被拉取、構(gòu)建后,作為參數(shù)傳遞給 outputs 函數(shù)  inputs = {    # flake inputs 有很多種引用方式,應(yīng)用最廣泛的是 github:owner/name/reference,即 github 倉(cāng)庫(kù)地址 + branch/commit-id/tag    # NixOS 官方軟件源,這里使用 nixos-unstable 分支    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";    # home-manager,用于管理用戶配置    home-manager = {      url = "github:nix-community/home-manager/release-22.11";      # `follows` 是 inputs 中的繼承語法      # 這里使 sops-nix 的 `inputs.nixpkgs` 與當(dāng)前 flake 的 `inputs.nixpkgs` 保持一致,      # 避免依賴的 nixpkgs 版本不一致導(dǎo)致問題      inputs.nixpkgs.follows = "nixpkgs";    };  };  # outputs 即 flake 的所有輸出,其中的 nixosConfigurations 即 NixOS 系統(tǒng)配置  # 一個(gè) flake 可以有很多用途,也可以有很多種不同的輸出,nixosConfigurations 只是其中一種  #  # outputs 的參數(shù)都是 inputs 中定義的依賴項(xiàng),可以通過它們的名稱來引用。  # 不過 self 是個(gè)例外,這個(gè)特殊參數(shù)指向 outputs 自身(自引用),以及 flake 根目錄  # 這里的 @ 語法將函數(shù)的參數(shù) attribute set 取了個(gè)別名,方便在內(nèi)部使用  outputs = { self, nixpkgs, ... }@inputs: {    # 名為 nixosConfigurations 的 outputs 會(huì)在執(zhí)行 `sudo nixos-rebuild switch` 時(shí)被使用    # 默認(rèn)情況下上述命令會(huì)使用與主機(jī) hostname 同名的 nixosConfigurations    # 但是也可以通過 `--flake /path/to/flake/direcotry#nixos-test` 來指定    # 在 flakes 配置文件夾中執(zhí)行 `sudo nixos-rebuild switch --flake .#nixos-test` 即可部署此配置    #   其中 `.` 表示使用當(dāng)前文件夾的 Flakes 配置,`#` 后面的內(nèi)容則是 nixosConfigurations 的名稱    nixosConfigurations = {      # hostname 為 nixos-test 的主機(jī)會(huì)使用這個(gè)配置      # 這里使用了 nixpkgs.lib.nixosSystem 函數(shù)來構(gòu)建配置,后面的 attributes set 是它的參數(shù)      # 在 nixos 系統(tǒng)上使用如下命令即可部署此配置:`nixos-rebuild switch --flake .#nixos-test`      "nixos-test" = nixpkgs.lib.nixosSystem {        system = "x86_64-linux";        # Nix 模塊系統(tǒng)可將配置模塊化,提升配置的可維護(hù)性        #        # modules 中每個(gè)參數(shù),都是一個(gè) Nix Module,nixpkgs manual 中有半份介紹它的文檔:        #            # 說半份是因?yàn)樗奈臋n不全,只有一些簡(jiǎn)單的介紹(Nix 文檔現(xiàn)狀...)        # Nix Module 可以是一個(gè) attribute set,也可以是一個(gè)返回 attribute set 的函數(shù)        # 如果是函數(shù),那么它的參數(shù)就是當(dāng)前的 NixOS Module 的參數(shù).        # 根據(jù) Nix Wiki 對(duì) Nix modules 的描述,Nix modules 函數(shù)的參數(shù)可以有這幾個(gè):        #        #  lib:     nixpkgs 自帶的函數(shù)庫(kù),提供了許多操作 Nix 表達(dá)式的實(shí)用函數(shù)        #           詳見 https://nixos.org/manual/nixpkgs/stable/#id-1.4        #  config:  當(dāng)前 flake 的所有 config 參數(shù)的集何        #  options: 當(dāng)前 flake 中所有 NixOS Modules 中定義的所有參數(shù)的集合        #  pkgs:    一個(gè)包含所有 nixpkgs 包的集合        #           入門階段可以認(rèn)為它的默認(rèn)值為 `nixpkgs.legacyPackages."${system}"`        #           可通過 `nixpkgs.pkgs` 這個(gè) option 來自定義 pkgs 的值        #  modulesPath: 默認(rèn) nixpkgs 的內(nèi)置 Modules 文件夾路徑,常用于從 nixpkgs 中導(dǎo)入一些額外的模塊        #               這個(gè)參數(shù)通常都用不到,我只在制作 iso 鏡像時(shí)用到過        #        # 默認(rèn)只能傳上面這幾個(gè)參數(shù),如果需要傳其他參數(shù),必須使用 specialArgs,你可以取消注釋如下這行來啟用該參數(shù)        # specialArgs = inputs  # 將 inputs 中的參數(shù)傳入所有子模塊        modules = [          # 導(dǎo)入之前我們使用的 configuration.nix,這樣舊的配置文件仍然能生效          # 注: /etc/nixos/configuration.nix 本身也是一個(gè) Nix Module,因此可以直接在這里導(dǎo)入          ./configuration.nix        ];      };    };  };}

這里我們定義了一個(gè)名為 nixos-test的系統(tǒng),它的配置文件為 ./configuration.nix,這個(gè)文件就是我們之前的配置文件,這樣我們?nèi)匀豢梢匝赜门f的配置。

現(xiàn)在執(zhí)行 sudo nixos-rebuild switch應(yīng)用配置,系統(tǒng)應(yīng)該沒有任何變化,因?yàn)槲覀儍H僅是切換到了 Nix Flakes,配置內(nèi)容與之前還是一致的。

4. 通過 Flakes 來管理系統(tǒng)軟件

切換完畢后,我們就可以通過 Flakes 來管理系統(tǒng)了。管系統(tǒng)最常見的需求就是裝軟件,我們?cè)谇懊嬉呀?jīng)見識(shí)過如何通過 environment.systemPackages來安裝 pkgs中的包,這些包都來自官方的 nixpkgs 倉(cāng)庫(kù)。

現(xiàn)在我們學(xué)習(xí)下如何通過 Flakes 安裝其他來源的軟件包,這比直接安裝 nixpkgs 要靈活很多,最顯而易見的好處是你可以很方便地設(shè)定軟件的版本。以 helix 編輯器為例,我們首先需要在 flake.nix中添加 helix 這個(gè) inputs 數(shù)據(jù)源:

{  description = "NixOS configuration of Ryan Yin";  # ......  inputs = {    # ......    # helix editor, use tag 23.05    helix.url = "github:helix-editor/helix/23.05"  };  outputs = inputs@{ self, nixpkgs, ... }: {    nixosConfigurations = {      nixos-test = nixpkgs.lib.nixosSystem {        system = "x86_64-linux";        # 將所有 inputs 參數(shù)設(shè)為所有子模塊的特殊參數(shù),這樣就能在子模塊中使用 helix 這個(gè) inputs 了        specialArgs = inputs;        modules = [          ./configuration.nix        ];      };    };  };}

接下來在 configuration.nix中就能引用這個(gè) flake input 數(shù)據(jù)源了:

# Edit this configuration file to define what should be installed on# your system.  Help is available in the configuration.nix(5) man page# and in the NixOS manual (accessible by running ‘nixos-help’).# Nix 會(huì)通過名稱匹配,自動(dòng)將 specialArgs 中的 helix 注入到此函數(shù)的第三個(gè)參數(shù){ config, pkgs, helix, ... }:{  # 省略掉前面的配置......  environment.systemPackages = with pkgs; [    git  # Nix Flakes 通過 git 命令從數(shù)據(jù)源拉取依賴,所以必須先安裝好 git    vim    wget    # 這里從 helix 這個(gè) inputs 數(shù)據(jù)源安裝了 helix 程序    helix."${pkgs.system}".packages.helix  ];  # 省略其他配置......}

改好后再 sudo nixos-rebuild switch部署,就能安裝好 helix 程序了,可直接在終端使用 helix命令測(cè)試驗(yàn)證。

5. 為 Flake 添加國(guó)內(nèi) cache 源

Nix 為了加快包構(gòu)建速度,提供了 https://cache.nixos.org 提前緩存構(gòu)建結(jié)果提供給用戶,但是在國(guó)內(nèi)訪問這個(gè) cache 地址非常地慢,如果沒有全局代理的話,基本上是無法使用的。另外 Flakes 的數(shù)據(jù)源基本都是某個(gè) Github 倉(cāng)庫(kù),在國(guó)內(nèi)從 Github 下載 Flakes 數(shù)據(jù)源也同樣非常非常慢。

在舊的 NixOS 配置方式中,可以通過 nix-channel命令添加國(guó)內(nèi)的 cache 鏡像源以提升下載速度,但是 Nix Flakes 會(huì)盡可能地避免使用任何系統(tǒng)級(jí)別的配置跟環(huán)境變量,以確保其構(gòu)建結(jié)果不受環(huán)境的影響,因此在使用了 Flakes 后 nix-channel命令就失效了。

為了自定義 cache 鏡像源,我們必須在 flake.nix中添加相關(guān)配置,這就是 nixConfig參數(shù),示例如下:

{  description = "NixOS configuration of Ryan Yin";  # 為了確保夠純,F(xiàn)lake 不依賴系統(tǒng)自身的 /etc/nix/nix.conf,而是在 flake.nix 中通過 nixConfig 設(shè)置  # 但是為了確保安全性,flake 默認(rèn)僅允許直接設(shè)置少數(shù) nixConfig 參數(shù),其他參數(shù)都需要在執(zhí)行 nix 命令時(shí)指定 `--accept-flake-config`,否則會(huì)被忽略  #       # 注意:即使添加了國(guó)內(nèi) cache 鏡像,如果有些包國(guó)內(nèi)鏡像下載不到,它仍然會(huì)走國(guó)外。  # 我的解法是使用 openwrt 旁路由 + openclash 加速下載。  # 臨時(shí)修改系統(tǒng)默認(rèn)網(wǎng)關(guān)為我的旁路由 IP:  #    sudo ip route add default via 192.168.5.201  # 還原路由規(guī)則:  #    sudo ip route del default via 192.168.5.201  nixConfig = {    experimental-features = [ "nix-command" "flakes" ];    substituters = [      # replace official cache with a mirror located in China      "https://mirrors.bfsu.edu.cn/nix-channels/store"      "https://cache.nixos.org/"    ];    # nix community"s cache server    extra-substituters = [      "https://nix-community.cachix.org"    ];    extra-trusted-public-keys = [      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="    ];  };  inputs = {    # 省略若干配置...  };  outputs = {    # 省略若干配置...  };}

改完后使用 sudo nixos-rebuild switch應(yīng)用配置即可生效,后續(xù)所有的包都會(huì)優(yōu)先從國(guó)內(nèi)鏡像源查找緩存。

注:上述手段只能加速部分包的下載,許多 inputs 數(shù)據(jù)源仍然會(huì)從 Github 拉取,另外如果找不到緩存,會(huì)執(zhí)行本地構(gòu)建,這通常仍然需要從國(guó)外下載源碼與構(gòu)建依賴,因此仍然會(huì)很慢。為了完全解決速度問題,仍然建議使用旁路由等局域網(wǎng)全局代理方案。

6. 安裝 home-manager

前面簡(jiǎn)單提過,NixOS 自身的配置文件只能管理系統(tǒng)級(jí)別的配置,而用戶級(jí)別的配置則需要使用 home-manager 來管理。

根據(jù)官方文檔 Home Manager Manual,要將 home manager 作為 NixOS 模塊安裝,首先需要?jiǎng)?chuàng)建 /etc/nixos/home.nix,配置方法如下:

{ config, pkgs, ... }:{  # 注意修改這里的用戶名與用戶目錄  home.username = "ryan";  home.homeDirectory = "/home/ryan";  # 直接將當(dāng)前文件夾的配置文件,鏈接到 Home 目錄下的指定位置  # home.file.".config/i3/wallpaper.jpg".source = ./wallpaper.jpg;  # 遞歸將某個(gè)文件夾中的文件,鏈接到 Home 目錄下的指定位置  # home.file.".config/i3/scripts" = {  #   source = ./scripts;  #   recursive = true;   # 遞歸整個(gè)文件夾  #   executable = true;  # 將其中所有文件添加「執(zhí)行」權(quán)限  # };  # 直接以 text 的方式,在 nix 配置文件中硬編碼文件內(nèi)容  # home.file.".xxx".text = ""  #     xxx  # "";  # set cursor size and dpi for 4k monitor  xresources.properties = {    "Xcursor.size" = 16;    "Xft.dpi" = 172;  };  # git 相關(guān)配置  programs.git = {    enable = true;    userName = "Ryan Yin";    userEmail = "xiaoyin_c@qq.com";  };  # Packages that should be installed to the user profile.  home.packages = [    pkgs.htop    pkgs.btop  ];  # 啟用 starship,這是一個(gè)漂亮的 shell 提示符  programs.starship = {    enable = true;    settings = {      add_newline = false;      aws.disabled = true;      gcloud.disabled = true;      line_break.disabled = true;    };  };  # alacritty 終端配置  programs.alacritty = {    enable = true;      env.TERM = "xterm-256color";      font = {        size = 12;        draw_bold_text_with_bright_colors = true;      };      scrolling.multiplier = 5;      selection.save_to_clipboard = true;  };  # This value determines the Home Manager release that your  # configuration is compatible with. This helps avoid breakage  # when a new Home Manager release introduces backwards  # incompatible changes.  #  # You can update Home Manager without changing this value. See  # the Home Manager release notes for a list of state version  # changes in each release.  home.stateVersion = "22.11";  # Let Home Manager install and manage itself.  programs.home-manager.enable = true;}

添加好 /etc/nixos/home.nix后,還需要在 /etc/nixos/flake.nix中導(dǎo)入該配置,它才能生效,可以使用如下命令,在當(dāng)前文件夾中生成一個(gè)示例配置以供參考:

nix flake new example -t github:nix-community/home-manager#nixos

調(diào)整好參數(shù)后的 /etc/nixos/flake.nix內(nèi)容示例如下:

{  description = "NixOS configuration";  inputs = {    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";    home-manager.url = "github:nix-community/home-manager";    home-manager.inputs.nixpkgs.follows = "nixpkgs";  };  outputs = inputs@{ nixpkgs, home-manager, ... }: {    nixosConfigurations = {      # 這里的 nixos-test 替換成你的主機(jī)名稱      nixos-test = nixpkgs.lib.nixosSystem {        system = "x86_64-linux";        modules = [          ./configuration.nix          # 將 home-manager 配置為 nixos 的一個(gè) module          # 這樣在 nixos-rebuild switch 時(shí),home-manager 配置也會(huì)被自動(dòng)部署          home-manager.nixosModules.home-manager          {            home-manager.useGlobalPkgs = true;            home-manager.useUserPackages = true;            # 這里的 ryan 也得替換成你的用戶名            # 這里的 import 函數(shù)在前面 Nix 語法中介紹過了,不再贅述            home-manager.users.ryan = import ./home.nix;            # 使用 home-manager.extraSpecialArgs 自定義傳遞給 ./home.nix 的參數(shù)            # 取消注釋下面這一行,就可以在 home.nix 中使用 flake 的所有 inputs 參數(shù)了            # home-manager.extraSpecialArgs = inputs;          }        ];      };    };  };}

然后執(zhí)行 sudo nixos-rebuild switch應(yīng)用配置,即可完成 home-manager 的安裝。

安裝完成后,所有用戶級(jí)別的程序、配置,都可以通過 /etc/nixos/home.nix管理,并且執(zhí)行 sudo nixos-rebuild switch時(shí)也會(huì)自動(dòng)應(yīng)用 home-manager 的配置。

home.nix中 Home Manager 的配置項(xiàng)有這幾種查找方式:

Home Manager - Appendix A. Configuration Options: 一份包含了所有配置項(xiàng)的列表,建議在其中關(guān)鍵字搜索。home-manager: 有些配置項(xiàng)在官方文檔中沒有列出,或者文檔描述不夠清晰,可以直接在這份 home-manager 的源碼中搜索閱讀對(duì)應(yīng)的源碼。7. 模塊化 NixOS 配置

到這里整個(gè)系統(tǒng)的骨架基本就配置完成了,當(dāng)前我們 /etc/nixos中的系統(tǒng)配置結(jié)構(gòu)應(yīng)該如下:

$ tree.├── flake.lock├── flake.nix├── home.nix└── configuration.nix

下面分別說明下這四個(gè)文件的功能:

flake.lock: 自動(dòng)生成的版本鎖文件,它記錄了整個(gè) flake 所有輸入的數(shù)據(jù)源、hash 值、版本號(hào),確保系統(tǒng)可復(fù)現(xiàn)。flake.nix: 入口文件,執(zhí)行 sudo nixos-rebuild switch時(shí)會(huì)識(shí)別并部署它。configuration.nix: 在 flake.nix 中被作為系統(tǒng)模塊導(dǎo)入,目前所有系統(tǒng)級(jí)別的配置都寫在此文件中。此配置文件中的所有配置項(xiàng),參見官方文檔 Configuration - NixOS Manualhome.nix: 在 flake.nix 中被 home-manager 作為 ryan 用戶的配置導(dǎo)入,也就是說它包含了 ryan 這個(gè)用戶的所有 Home Manager 配置,負(fù)責(zé)管理其 Home 文件夾。此配置文件的所有配置項(xiàng),參見 Appendix A. Configuration Options - Home Manager

通過修改上面幾個(gè)配置文件就可以實(shí)現(xiàn)對(duì)系統(tǒng)與 Home 目錄狀態(tài)的修改。但是隨著配置的增多,單純依靠 configuration.nixhome.nix會(huì)導(dǎo)致配置文件臃腫,難以維護(hù),因此更好的解決方案是通過 Nix 的模塊機(jī)制,將配置文件拆分成多個(gè)模塊,分門別類地編寫維護(hù)。

在前面的 Nix 語法一節(jié)有介紹過:「import的參數(shù)如果為文件夾路徑,那么會(huì)返回該文件夾下的 default.nix文件的執(zhí)行結(jié)果」,實(shí)際 Nix 還提供了一個(gè) imports參數(shù),它可以接受一個(gè) .nix文件列表,并將該列表中的所有配置合并(Merge)到當(dāng)前的 attribute set 中。注意這里的用詞是「合并」,它表明 imports如果遇到重復(fù)的配置項(xiàng),不會(huì)簡(jiǎn)單地按執(zhí)行順序互相覆蓋,而是更合理地處理。比如說我在多個(gè) modules 中都定義了 program.packages = [...],那么 imports會(huì)將所有 modules 中的 program.packages這個(gè) list 合并。不僅 list 能被正確合并,attribute set 也能被正確合并,具體行為各位看官可以自行探索。

我只在 nixpkgs-unstable 官方手冊(cè) - evalModules parameters 中找到一句關(guān)于 imports的描述:A list of modules. These are merged together to form the final configuration.,可以意會(huì)一下...(Nix 的文檔真的一言難盡...這么核心的參數(shù)文檔就這么一句...)

我們可以借助 imports參數(shù),將 home.nixconfiguration.nix拆分成多個(gè) .nix文件。

比如我之前的 i3wm 系統(tǒng)配置 ryan4yin/nix-config/v0.0.2,結(jié)構(gòu)如下:

├── flake.lock├── flake.nix├── home│   ├── default.nix         # 在這里通過 imports = [...] 導(dǎo)入所有子模塊│   ├── fcitx5              # fcitx5 中文輸入法設(shè)置,我使用了自定義的小鶴音形輸入法│   │   ├── default.nix│   │   └── rime-data-flypy│   ├── i3                  # i3wm 桌面配置│   │   ├── config│   │   ├── default.nix│   │   ├── i3blocks.conf│   │   ├── keybindings│   │   └── scripts│   ├── programs│   │   ├── browsers.nix│   │   ├── common.nix│   │   ├── default.nix   # 在這里通過 imports = [...] 導(dǎo)入 programs 目錄下的所有 nix 文件│   │   ├── git.nix│   │   ├── media.nix│   │   ├── vscode.nix│   │   └── xdg.nix│   ├── rofi              #  rofi 應(yīng)用啟動(dòng)器配置,通過 i3wm 中配置的快捷鍵觸發(fā)│   │   ├── configs│   │   │   ├── arc_dark_colors.rasi│   │   │   ├── arc_dark_transparent_colors.rasi│   │   │   ├── power-profiles.rasi│   │   │   ├── powermenu.rasi│   │   │   ├── rofidmenu.rasi│   │   │   └── rofikeyhint.rasi│   │   └── default.nix│   └── shell             # shell 終端相關(guān)配置│       ├── common.nix│       ├── default.nix│       ├── nushell│       │   ├── config.nu│       │   ├── default.nix│       │   └── env.nu│       ├── starship.nix│       └── terminals.nix├── hosts│   ├── msi-rtx4090      # PC 主機(jī)的配置│   │   ├── default.nix                 # 這就是之前的 configuration.nix,不過大部分內(nèi)容都拆出到 modules 了│   │   └── hardware-configuration.nix  # 與系統(tǒng)硬件相關(guān)的配置,安裝 nixos 時(shí)自動(dòng)生成的│   └── nixos-test       # 測(cè)試用的虛擬機(jī)配置│       ├── default.nix│       └── hardware-configuration.nix├── modules          # 從 configuration.nix 中拆分出的一些通用配置│   ├── i3.nix│   └── system.nix└── wallpaper.jpg    # 桌面壁紙,在 i3wm 配置中被引用

詳細(xì)結(jié)構(gòu)與內(nèi)容,請(qǐng)移步前面提供的 github 倉(cāng)庫(kù)鏈接,這里就不多介紹了。

8. 更新系統(tǒng)

在使用了 Nix Flakes 后,要更新系統(tǒng)也很簡(jiǎn)單,先更新 flake.lock 文件,然后部署即可。在配置文件夾中執(zhí)行如下命令:

# 更新 flake.locknix flake update# 部署系統(tǒng)sudo nixos-rebuild switch

另外有時(shí)候安裝新的包,跑 sudo nixos-rebuild switch時(shí)可能會(huì)遇到 sha256 不匹配的報(bào)錯(cuò),也可以嘗試通過 nix flake update更新 flake.lock 來解決(原理暫時(shí)不太清楚)。

9. 回退個(gè)別軟件包的版本

在使用 Nix Flakes 后,目前大家用得比較多的都是 nixos-unstable分支的 nixpkgs,有時(shí)候就會(huì)遇到一些 bug,比如我最近(2023/5/6)就遇到了 chrome/vscode 閃退的問題。

這時(shí)候就需要退回到之前的版本,在 Nix Flakes 中,所有的包版本與 hash 值與其 input 數(shù)據(jù)源的 git commit 是一一對(duì)應(yīng)的關(guān)系,因此回退某個(gè)包的到歷史版本,就需要鎖定其 input 數(shù)據(jù)源的 git commit.

為了實(shí)現(xiàn)上述需求,首先修改 /etc/nixos/flake.nix,示例內(nèi)容如下(主要是利用 specialArgs參數(shù)):

{  description = "NixOS configuration of Ryan Yin"  inputs = {    # 默認(rèn)使用 nixos-unstable 分支    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";    # 最新 stable 分支的 nixpkgs,用于回退個(gè)別軟件包的版本,當(dāng)前最新版本為 22.11    nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-22.11";    # 另外也可以使用 git commit hash 來鎖定版本,這是最徹底的鎖定方式    nixpkgs-fd40cef8d.url = "github:nixos/nixpkgs/fd40cef8d797670e203a27a91e4b8e6decf0b90c";  };  outputs = inputs@{    self,    nixpkgs,    nixpkgs-stable,    nixpkgs-fd40cef8d,    ...  }: {    nixosConfigurations = {      nixos-test = nixpkgs.lib.nixosSystem rec {        system = "x86_64-linux";        # 核心參數(shù)是這個(gè),將非默認(rèn)的 nixpkgs 數(shù)據(jù)源傳到其他 modules 中        specialArgs = {          # 注意每次 import 都會(huì)生成一個(gè)新的 nixpkgs 實(shí)例          # 這里我們直接在 flake.nix 中創(chuàng)建實(shí)例, 再傳遞到其他子 modules 中使用          # 這樣能有效重用 nixpkgs 實(shí)例,避免 nixpkgs 實(shí)例泛濫。          pkgs-stable = import nixpkgs-stable {            system = system;  # 這里遞歸引用了外部的 system 屬性            # 為了拉取 chrome 等軟件包,需要允許安裝非自由軟件            config.allowUnfree = true;          };          pkgs-fd40cef8d = import nixpkgs-fd40cef8d {            system = system;            config.allowUnfree = true;          };        };        modules = [          ./hosts/nixos-test          # 省略其他模塊配置...        ];      };    };  };}

然后在你對(duì)應(yīng)的 module 中使用該數(shù)據(jù)源中的包,一個(gè) Home Manager 的子模塊示例:

{  pkgs,  config,  # nix 會(huì)從 flake.nix 的 specialArgs 查找并注入此參數(shù)  pkgs-stable,  # pkgs-fd40cef8d,  # 也可以使用固定 hash 的 nixpkgs 數(shù)據(jù)源  ...}:{  # 這里從 pkg-stable 中引用包  home.packages = with pkgs-stable; [    firefox-wayland    # chrome wayland support was broken on nixos-unstable branch, so fallback to stable branch for now    # https://github.com/swaywm/sway/issues/7562    google-chrome  ];  programs.vscode = {    enable = true;    package = pkgs-stable.vscode;  # 這里也一樣,從 pkgs-stable 中引用包  };}

配置完成后,通過 sudo nixos-rebuild switch部署即可將 firefox/chrome/vscode 三個(gè)軟件包回退到 stable 分支的版本。

根據(jù) @fbewivpjsbsby 補(bǔ)充的文章 1000 instances of nixpkgs,在子模塊中用 import來定制 nixpkgs不是一個(gè)好的習(xí)慣,因?yàn)槊看?import都會(huì)重新求值并產(chǎn)生一個(gè)新的 nixpkgs 實(shí)例,在配置越來越多時(shí)會(huì)導(dǎo)致構(gòu)建時(shí)間變長(zhǎng)、內(nèi)存占用變大。所以這里改為了在 flake.nix中創(chuàng)建所有 nixpkgs 實(shí)例。

10. 使用 Git 管理 NixOS 配置

NixOS 的配置文件是純文本,因此跟普通的 dotfiles 一樣可以使用 Git 管理。

此外 Nix Flakes 配置也不一定需要放在 /etc/nixos目錄下,可以放在任意目錄下,只要在部署時(shí)指定正確的路徑即可。

我們?cè)谇懊娴?3 小節(jié)的代碼注釋中有說明過,可以通過 sudo nixos-rebuild switch --flake .#xxx--flake參數(shù)指定 Flakes 配置的文件夾路徑,并通過 #后面的值來指定使用的 outputs 名稱。

比如我的使用方式是將 Nix Flakes 配置放在 ~/nixos-config目錄下,然后在 /etc/nixos目錄下創(chuàng)建一個(gè)軟鏈接:

sudo mv /etc/nixos /etc/nixos.bak  # 備份原來的配置sudo ln -s ~/nixos-config/ /etc/nixos

然后就可以在 ~/nixos-config目錄下使用 Git 管理配置了,配置使用普通的用戶級(jí)別權(quán)限即可,不要求 owner 為 root.

另一種方法是直接刪除掉 /etc/nixos,并在每次部署時(shí)指定配置文件路徑:

sudo mv /etc/nixos /etc/nixos.bak  # 備份原來的配置cd ~/nixos-config# 通過 --flake .#nixos-test 參數(shù),指定使用當(dāng)前文件夾的 flake.nix,使用的 nixosConfiguraitons 名稱為 nixos-testsudo nixos-rebuild switch --flake .#nixos-test

兩種方式都可以,看個(gè)人喜好。

11. 查看與清理歷史數(shù)據(jù)

如前所述,NixOS 的每次部署都會(huì)生成一個(gè)新的版本,所有版本都會(huì)被添加到系統(tǒng)啟動(dòng)項(xiàng)中,除了重啟電腦外,我們也可以通過如下命令查詢當(dāng)前可用的所有歷史版本:

nix profile history --profile /nix/var/nix/profiles/system

以及清理歷史版本釋放存儲(chǔ)空間的命令:

# 清理 7 天之前的所有歷史版本sudo nix profile wipe-history --profile /nix/var/nix/profiles/system  --older-than 7d# 清理歷史版本并不會(huì)刪除數(shù)據(jù),還需要手動(dòng) gc 下sudo nix store gc --debug

以及查看系統(tǒng)層面安裝的所有軟件包(這個(gè)貌似只能用 nix-env):

nix-env -qa
七、Nix Flakes 的使用

到這里我們已經(jīng)寫了不少 Nix Flakes 配置來管理 NixOS 系統(tǒng)了,這里再簡(jiǎn)單介紹下 Nix Flakes 更細(xì)節(jié)的內(nèi)容,以及常用的 nix flake 命令。

1. Flake 的 inputs

flake.nix中的 inputs是一個(gè) attribute set,用來指定當(dāng)前 Flake 的依賴,inputs 有很多種類型,舉例如下:

{  inputs = {    # 以 GitHub 倉(cāng)庫(kù)為數(shù)據(jù)源,指定使用 master 分支,這是最常見的 input 格式    nixpkgs.url = "github:Mic92/nixpkgs/master";    # Git URL,可用于任何基于 https/ssh 協(xié)議的 Git 倉(cāng)庫(kù)    git-example.url = "git+https://git.somehost.tld/user/path?ref=branch&rev=fdc8ef970de2b4634e1b3dca296e1ed918459a9e";    # 上面的例子會(huì)復(fù)制 .git 到本地, 如果數(shù)據(jù)量較大,建議使用 shallow=1 參數(shù)避免復(fù)制 .git    git-directory-example.url = "git+file:/path/to/repo?shallow=1";    # 本地文件夾 (如果使用絕對(duì)路徑,可省略掉前綴 "path:")    directory-example.url = "path:/path/to/repo";    # 如果數(shù)據(jù)源不是一個(gè) flake,則需要設(shè)置 flake=false    bar = {      url = "github:foo/bar/branch";      flake = false;    };    sops-nix = {      url = "github:Mic92/sops-nix";      # `follows` 是 inputs 中的繼承語法      # 這里使 sops-nix 的 `inputs.nixpkgs` 與當(dāng)前 flake 的 inputs.nixpkgs 保持一致,      # 避免依賴的 nixpkgs 版本不一致導(dǎo)致問題      inputs.nixpkgs.follows = "nixpkgs";    };    # 將 flake 鎖定在某個(gè) commit 上    nix-doom-emacs = {      url = "github:vlaci/nix-doom-emacs?rev=238b18d7b2c8239f676358634bfb32693d3706f3";      flake = false;    };    # 使用 `dir` 參數(shù)指定某個(gè)子目錄    nixpkgs.url = "github:foo/bar?dir=shu";  }}
2. Flake 的 outputs

flake.nix中的 outputs是一個(gè) attribute set,是整個(gè) Flake 的構(gòu)建結(jié)果,每個(gè) Flake 都可以有許多不同的 outputs。

一些特定名稱的 outputs 有特殊用途,會(huì)被某些 Nix 命令識(shí)別處理,比如:

Nix packages: 名稱為 apps.., packages..legacyPackages..的 outputs,都是 Nix 包,通常都是一個(gè)個(gè)應(yīng)用程序。可以通過 nix build .#name來構(gòu)建某個(gè) nix 包Nix Helper Functions: 名稱為 lib的 outputs 是 Flake 函數(shù)庫(kù),可以被其他 Flake 作為 inputs 導(dǎo)入使用。Nix development environments: 名稱為 devShells的 outputs 是 Nix 開發(fā)環(huán)境可以通過 nix develop命令來使用該 Output 創(chuàng)建開發(fā)環(huán)境NixOS configurations: 名稱為 nixosConfigurations.的 outputs,是 NixOS 的系統(tǒng)配置。nixos-rebuild switch .#可以使用該 Output 來部署 NixOS 系統(tǒng)Nix templates: 名稱為 templates的 outputs 是 flake 模板可以通過執(zhí)行命令 nix flake init --template 使用模板初始化一個(gè) Flake 包其他用戶自定義的 outputs,可能被其他 Nix 相關(guān)的工具使用

NixOS Wiki 中給出的使用案例:

{ self, ... }@inputs:{  # Executed by `nix flake check`  checks.""."" = derivation;  # Executed by `nix build .#`  packages.""."" = derivation;  # Executed by `nix build .`  packages."".default = derivation;  # Executed by `nix run .#`  apps.""."" = {    type = "app";    program = "";  };  # Executed by `nix run . -- `  apps."".default = { type = "app"; program = "..."; };  # Formatter (alejandra, nixfmt or nixpkgs-fmt)  formatter."" = derivation;  # Used for nixpkgs packages, also accessible via `nix build .#`  legacyPackages.""."" = derivation;  # Overlay, consumed by other flakes  overlays."" = final: prev: { };  # Default overlay  overlays.default = {};  # Nixos module, consumed by other flakes  nixosModules."" = { config }: { options = {}; config = {}; };  # Default module  nixosModules.default = {};  # Used with `nixos-rebuild --flake .#`  # nixosConfigurations."".config.system.build.toplevel must be a derivation  nixosConfigurations."" = {};  # Used by `nix develop .#`  devShells.""."" = derivation;  # Used by `nix develop`  devShells."".default = derivation;  # Hydra build jobs  hydraJobs.""."" = derivation;  # Used by `nix flake init -t #`  templates."" = {    path = "";    description = "template description goes here?";  };  # Used by `nix flake init -t `  templates.default = { path = ""; description = ""; };}
3. Flake 命令行的使用

在啟用了 nix-command& flakes功能后,我們就可以使用 Nix 提供的新一代 Nix 命令行工具 New Nix Commands 了,下面列舉下其中常用命令的用法:

# 解釋下這條指令涉及的參數(shù):#   `nixpkgs#ponysay` 意思是 `nixpkgs` 這個(gè) flake 中的 `ponysay` 包。#   `nixpkgs` 是一個(gè) flakeregistry ida,#    nix 會(huì)從  中#    找到這個(gè) id 對(duì)應(yīng)的 github 倉(cāng)庫(kù)地址# 所以這個(gè)命令的意思是創(chuàng)建一個(gè)新環(huán)境,安裝并運(yùn)行 `nixpkgs` 這個(gè) flake 提供的 `ponysay` 包。#   注:前面已經(jīng)介紹過了,nix 包 是 flake outputs 中的一種。echo "Hello Nix" | nix run "nixpkgs#ponysay"# 這條命令和上面的命令作用是一樣的,只是使用了完整的 flake URI,而不是 flakeregistry id。echo "Hello Nix" | nix run "github:NixOS/nixpkgs/nixos-unstable#ponysay"# 這條命令的作用是使用 zero-to-nix 這個(gè) flake 中名 `devShells.example` 的 outptus 來創(chuàng)建一個(gè)開發(fā)環(huán)境,# 然后在這個(gè)環(huán)境中打開一個(gè) bash shell。nix develop "github:DeterminateSystems/zero-to-nix#example"# 除了使用遠(yuǎn)程 flake uri 之外,你也可以使用當(dāng)前目錄下的 flake 來創(chuàng)建一個(gè)開發(fā)環(huán)境。mkdir my-flake && cd my-flake## 通過模板初始化一個(gè) flakenix flake init --template "github:DeterminateSystems/zero-to-nix#javascript-dev"## 使用當(dāng)前目錄下的 flake 創(chuàng)建一個(gè)開發(fā)環(huán)境,并打開一個(gè) bash shellnix develop# 或者如果你的 flake 有多個(gè) devShell 輸出,你可以指定使用名為 example 的那個(gè)nix develop .#example# 構(gòu)建 `nixpkgs` flake 中的 `bat` 這個(gè)包# 并在當(dāng)前目錄下創(chuàng)建一個(gè)名為 `result` 的符號(hào)鏈接,鏈接到該構(gòu)建結(jié)果文件夾。mkdir build-nix-package && cd build-nix-packagenix build "nixpkgs#bat"# 構(gòu)建一個(gè)本地 flake 和 nix develop 是一樣的,不再贅述

此外 Zero to Nix - Determinate Systems 是一份全新的 Nix & Flake 新手入門文檔,寫得比較淺顯易懂,適合新手用來入門。

八、Nixpkgs 的高級(jí)用法

callPackage、Overriding 與 Overlays 是在使用 Nix 時(shí)偶爾會(huì)用到的技術(shù),它們都是用來自定義 Nix 包的構(gòu)建方法的。

我們知道許多程序都有大量構(gòu)建參數(shù)需要配置,不同的用戶會(huì)希望使用不同的構(gòu)建參數(shù),這時(shí)候就需要 Overriding 與 Overlays 來實(shí)現(xiàn)。我舉幾個(gè)我遇到過的例子:

fcitx5-rime.nix: fcitx5-rime 的 rimeDataPkgs默認(rèn)使用 rime-data包,但是也可以通過 override 來自定義該參數(shù)的值,以加載自定義的 rime 配置(比如加載小鶴音形輸入法配置)。vscode/with-extensions.nix: vscode 的這個(gè)包也可以通過 override 來自定義 vscodeExtensions參數(shù)的值來安裝自定義插件。nix-vscode-extensions: 就是利用該參數(shù)實(shí)現(xiàn)的 vscode 插件管理firefox/common.nix: firefox 同樣有許多可自定義的參數(shù)等等

總之如果需要自定義上述這類 Nix 包的構(gòu)建參數(shù),或者實(shí)施某些比較底層的修改,我們就得用到 Overriding 跟 Overlays。

Overriding

Chapter 4. Overriding - nixpkgs Manual

簡(jiǎn)單的說,所有 nixpkgs 中的 Nix 包都可以通過 .override {}來自定義某些構(gòu)建參數(shù),它返回一個(gè)使用了自定義參數(shù)的新 Derivation. 舉個(gè)例子:

pkgs.fcitx5-rime.override {rimeDataPkgs = [    ./rime-data-flypy];}

上面這個(gè) Nix 表達(dá)式的執(zhí)行結(jié)果就是一個(gè)新的 Derivation,它的 rimeDataPkgs參數(shù)被覆蓋為 [./rime-data-flypy],而其他參數(shù)則沿用原來的值。

除了覆寫參數(shù),還可以通過 overrideAttrs來覆寫使用 stdenv.mkDerivation構(gòu)建的 Derivation 的屬性,比如:

helloWithDebug = pkgs.hello.overrideAttrs (finalAttrs: previousAttrs: {  separateDebugInfo = true;});

上面這個(gè)例子中,helloWithDebug就是一個(gè)新的 Derivation,它的 separateDebugInfo參數(shù)被覆蓋為 true,而其他參數(shù)則沿用原來的值。

Overlays

Chapter 3. Overlays - nixpkgs Manual

前面介紹的 override 函數(shù)都會(huì)生成新的 Derivation,不影響 pkgs 中原有的 Derivation,只適合作為局部參數(shù)使用。但如果你需要覆寫的 Derivation 還被其他 Nix 包所依賴,那其他 Nix 包使用的仍然會(huì)是原有的 Derivation.

為了解決這個(gè)問題,Nix 提供了 overlays 能力。簡(jiǎn)單的說,Overlays 可以全局修改 pkgs 中的 Derivation。

在舊的 Nix 環(huán)境中,Nix 默認(rèn)會(huì)自動(dòng)應(yīng)用 ~/.config/nixpkgs/overlays.nix~/.config/nixpkgs/overlays/*.nix這類路徑下的所有 overlays 配置。

但是在 Flakes 中,為了確保系統(tǒng)的可復(fù)現(xiàn)性,它不能依賴任何 Git 倉(cāng)庫(kù)之外的配置,所以這種舊的方法就不能用了。

在使用 Nix Flakes 編寫 NixOS 配置時(shí),Home Manager 與 NixOS 都提供了 nixpkgs.overlays這個(gè) option 來引入 overlays, 相關(guān)文檔:

home-manager docs - nixpkgs.overlaysnixpkgs source code - nixpkgs.overlays

舉個(gè)例子,如下內(nèi)容就是一個(gè)加載 Overlays 的 Module,它既可以用做 Home Manager Module,也可以用做 NixOS Module,因?yàn)檫@倆定義完全是一致的:

不過我使用發(fā)現(xiàn),Home Manager 畢竟是個(gè)外部組件,而且現(xiàn)在全都用的 unstable 分支,這導(dǎo)致 Home Manager Module 有時(shí)候會(huì)有點(diǎn)小毛病,因此更建議以 NixOS Module 的形式引入 overlays

{ config, pkgs, lib, ... }:{  nixpkgs.overlays = [    # overlayer1 - 參數(shù)名用 self 與 super,表達(dá)繼承關(guān)系    (self: super: {     google-chrome = super.google-chrome.override {       commandLineArgs =         "--proxy-server="https=127.0.0.1:3128;http=127.0.0.1:3128"";     };    })    # overlayer2 - 還可以使用 extend 來繼承其他 overlay    # 這里改用 final 與 prev,表達(dá)新舊關(guān)系    (final: prev: {      steam = prev.steam.override {        extraPkgs = pkgs:          with pkgs; [            keyutils            libkrb5            libpng            libpulseaudio            libvorbis            stdenv.cc.cc.lib            xorg.libXcursor            xorg.libXi            xorg.libXinerama            xorg.libXScrnSaver          ];        extraProfile = "export GDK_SCALE=2";      };    })    # overlay3 - 也可以將 overlay 定義在其他文件中    (import ./overlays/overlay3.nix)  ];}

這里只是個(gè)示例配置,參照此格式編寫你自己的 overlays 配置,將該配置作為 NixOS Module 或者 Home Manager Module 引入,然后部署就可以看到效果了。

模塊化 overlays 配置

上面的例子說明了如何編寫 overlays,但是所有 overlays 都一股腦兒寫在一起,就有點(diǎn)難以維護(hù)了,寫得多了自然就希望模塊化管理這些 overlays.

這里介紹下我找到的一個(gè) overlays 模塊化管理的最佳實(shí)踐。

首先在 Git 倉(cāng)庫(kù)中創(chuàng)建 overlays文件夾用于存放所有 overlays 配置,然后創(chuàng)建 overlays/default.nix,其內(nèi)容如下:

args:  # import 當(dāng)前文件夾下所有的 nix 文件,并以 args 為參數(shù)執(zhí)行它們  # 返回值是一個(gè)所有執(zhí)行結(jié)果的列表,也就是 overlays 的列表  builtins.map  (f: (import (./. + "/${f}") args))  # map 的第一個(gè)參數(shù),是一個(gè) import 并執(zhí)行 nix 文件的函數(shù)  (builtins.filter          # map 的第二個(gè)參數(shù),它返回一個(gè)當(dāng)前文件夾下除 default.nix 外所有 nix 文件的列表    (f: f != "default.nix")    (builtins.attrNames (builtins.readDir ./.)))

后續(xù)所有 overlays 配置都添加到 overlays文件夾中,一個(gè)示例配置 overlays/fcitx5/default.nix內(nèi)容如下:

# 為了不使用默認(rèn)的 rime-data,改用我自定義的小鶴音形數(shù)據(jù),這里需要 override# 參考 https://github.com/NixOS/nixpkgs/blob/e4246ae1e7f78b7087dce9c9da10d28d3725025f/pkgs/tools/inputmethods/fcitx5/fcitx5-rime.nix{pkgs, config, lib, ...}:(self: super: {  # 小鶴音形配置,配置來自 flypy.com 官方網(wǎng)盤的鼠須管配置壓縮包「小鶴音形“鼠須管”for macOS.zip」  rime-data = ./rime-data-flypy;  fcitx5-rime = super.fcitx5-rime.override { rimeDataPkgs = [ ./rime-data-flypy ]; };})

我通過上面這個(gè) overlays 修改了 fcitx5-rime 輸入法的默認(rèn)數(shù)據(jù),加載了我自定義的小鶴音形輸入法。

最后,還需要通過 nixpkgs.overlays這個(gè) option 加載 overlays/default.nix返回的所有 overlays 配置,在任一 NixOS Module 中添加如下參數(shù)即可:

{ config, pkgs, lib, ... } @ args:{  # ......  # 添加此參數(shù)  nixpkgs.overlays = import /path/to/overlays/dir;  # ......}

比如說直接寫 flake.nix里:

{  description = "NixOS configuration of Ryan Yin";  # ......  inputs = {    # ......  };  outputs = inputs@{ self, nixpkgs, ... }: {    nixosConfigurations = {      nixos-test = nixpkgs.lib.nixosSystem {        system = "x86_64-linux";        specialArgs = inputs;        modules = [          ./hosts/nixos-test          # 添加如下內(nèi)嵌 module 定義          #   這里將 modules 的所有參數(shù) args 都傳遞到了 overlays 中          (args: { nixpkgs.overlays = import ./overlays args; })          # ......        ];      };    };  };}

按照上述方法進(jìn)行配置,就可以很方便地模塊化管理所有 overlays 配置了,以我的配置為例,overlays 文件夾的結(jié)構(gòu)大致如下:

.├── flake.lock├── flake.nix├── home├── hosts├── modules├── ......├── overlays│   ├── default.nix         # 它返回一個(gè)所有 overlays 的列表│   └── fcitx5              # fcitx5 overlay│       ├── default.nix│       ├── README.md│       └── rime-data-flypy  # 自定義的 rime-data,需要遵循它的文件夾格式│           └── share│               └── rime-data│                   ├── ......  # rime-data 文件└── README.md

你可以在我的配置倉(cāng)庫(kù) ryan4yin/nix-config/v0.0.4 查看更詳細(xì)的內(nèi)容,獲取些靈感。

進(jìn)階玩法

逐漸熟悉 Nix 這一套工具鏈后,可以進(jìn)一步讀一讀 Nix 的三本手冊(cè),挖掘更多的玩法:

Nix Reference Manual: Nix 包管理器使用手冊(cè),主要包含 Nix 包管理器的設(shè)計(jì)、命令行使用說明。nixpkgs Manual: 主要介紹 Nixpkgs 的參數(shù)、Nix 包的使用、修改、打包方法。NixOS Manual: NixOS 系統(tǒng)使用手冊(cè),主要包含 Wayland/X11, GPU 等系統(tǒng)級(jí)別的配置說明。nix-pills: Nix Pills 對(duì)如何使用 Nix 構(gòu)建軟件包進(jìn)行了深入的闡述,寫得比官方文檔清晰易懂,而且也足夠深入,值得一讀。

在對(duì) Nix Flakes 熟悉到一定程度后,你可以嘗試一些 Flakes 的進(jìn)階玩法,如下是一些比較流行的社區(qū)項(xiàng)目,可以試用:

flake-parts: 通過 Module 模塊系統(tǒng)簡(jiǎn)化配置的編寫與維護(hù)。flake-utils-plus:同樣是用于簡(jiǎn)化 Flake 配置的第三方包,不過貌似更強(qiáng)大些digga: 一個(gè)大而全的 Flake 模板,揉合了各種實(shí)用 Nix 工具包的功能,不過結(jié)構(gòu)比較復(fù)雜,需要一定經(jīng)驗(yàn)才能玩得轉(zhuǎn)。......

以及其他許多實(shí)用的社區(qū)項(xiàng)目可探索,我比較關(guān)注的有這幾個(gè):

devenv: 開發(fā)環(huán)境管理agenix: secrets 管理nixos-generator: 鏡像生成工具,從 nixos 配置生成 iso/qcow2 等格式的鏡像lanzaboote: 啟用 secure bootimpermanence: 用于配置無狀態(tài)系統(tǒng)。可用它持久化你指定的文件或文件夾,同時(shí)再將 /home 目錄掛載為 tmpfs 或者每次啟動(dòng)時(shí)用工具擦除一遍。這樣所有不受 impermanence 管理的數(shù)據(jù)都將成為臨時(shí)數(shù)據(jù),如果它們導(dǎo)致了任何問題,重啟下系統(tǒng)這些數(shù)據(jù)就全部還原到初始狀態(tài)了!colmena: NixOS 主機(jī)部署工具總結(jié)

這是本系列文章的第一篇,介紹了使用 Nix Flakes 配置 NixOS 系統(tǒng)的基礎(chǔ)知識(shí),跟著這篇文章把系統(tǒng)配置好,就算是入門了。

我會(huì)在后續(xù)文章中介紹 NixOS & Nix Flakes 的進(jìn)階知識(shí):開發(fā)環(huán)境管理、secrets 管理、軟件打包、遠(yuǎn)程主機(jī)管理等等,盡請(qǐng)期待。

參考

如下是我參考過的比較有用的 Nix 相關(guān)資料:

Zero to Nix - Determinate Systems: 淺顯易懂的 Nix Flakes 新手入門文檔,值得一讀。NixOS 系列: 這是 LanTian 大佬的 NixOS 系列文章,寫得非常清晰明了,新手必讀。Nix Flakes Series: 官方的 Nix Flakes 系列文章,介紹得比較詳細(xì),作為新手入門比較 OKNix Flakes - Wiki: Nix Flakes 的官方 Wiki,此文介紹得比較粗略。ryan4yin/nix-config: 我的 NixOS 配置倉(cāng)庫(kù),README 中也列出了我參考過的其他配置倉(cāng)庫(kù)

標(biāo)簽: