使用 Nixtamal 构建 NixOS

Lobsters Hottest 工具

摘要

一本食谱风格的指南,演示如何使用 Nixtamal 构建 NixOS 系统。Nixtamal 是一种输入固定工具,用于取代有状态的 nix-channel 工作流。文中还重点介绍了 NixOS 26.05 中引入的全新 system.nix 入口点。

<p><a href="https://lobste.rs/s/4x3ten/building_nixos_using_nixtamal">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/29 15:42

# 使用 Nixtamal 构建 NixOS | Nixtamal Source: https://nixtamal.toast.al/cookbook/nixos-using-nixtamal/ ## 简介 NixOS (https://nixos.org/) 是 Nix 的演进与自然成果——如果你能够可靠地、可复现地、跨不同机器地构建软件,那么与其止步于独立软件,不如让一批软件加上一个引导过程来构建一个操作系统——同样具备从糟糕状态回滚等保障能力。 初学者常常遇到的一个问题是 `nix-channel` 命令[\[\*\]](https://nixtamal.toast.al/cookbook/nixos-using-nixtamal/#footnote-1),它通常不建议长期使用,因为它与机器状态相关联,会导致状态漂移,使得在这台特定机器上难以复现,而当谈论构建多台机器、团队协作与部署时,复现几乎变得不可能。那应该推荐什么呢?解决方案可以追溯到 Nixpkgs 的诞生之初:输入固定 (input pinning) (https://dev.reference/pinning-nixpkgs.html)。虽然固定可以用 `builtins.fetchTarball` 这样简单的工具完成——它甚至不需要 Nixpkgs,更不用说特殊功能——但随着时间推移,更新固定的值与哈希会变得繁琐。这就是一类被称为「输入固定器」(input pinners) 的 Nix 软件背后的设计动机,它们会自动更新固定的值与哈希。本文演示的输入固定器是 Nixtamal (https://nixtamal.toast.al/),与其他替代方案相比,它提供了独特的功能集 (https://nixtamal.toast.al/#key-features),而且不需要*任何*实验性的、不安全的特性,因此你可以放心依赖它[\[†\]](https://nixtamal.toast.al/cookbook/nixos-using-nixtamal/#footnote-2)。 我们将通过一系列包含 overlay、模块与输入类型的项目来介绍新的入口文件 `system.nix`,这些项目同时展示了 Nixtamal 与稳定的 NixOS 配置方式。 **注意** 此处提到的代码反映的是撰写本文时的特定状态。未来可能会有些许不同,Nixtamal 的 API 也可能有细微变化。 ## NixOS 26.05 带来了 `system.nix` 在 Nix 26.05 发行说明 (https://nixos.org/manual/nixos/stable/release-notes#sec-release-26.05) 中,`system.nix` 被介绍为: > 作为 `configuration.nix`(以及 `flake.nix`)的替代入口点,允许在不使用 `nix-channel` 的情况下配置 NixOS。该文件必须求值为一个 NixOS 系统派生,或此类派生的属性集;在这种情况下,需要通过 `nixos-rebuild` 或 `nixos-install` 的 `--attr` 选项来选择要构建的属性。 这正是想要简单配置的人所需要的,让其成为系统入口! **注意** 在 `system.nix` 之前也有其他方式可以实现类似的结果,但 `system.nix` 是更好的支持、经过深思熟虑、随 Nixpkgs 一起发布的推荐方式。 ## 一次非平凡的 NixOS 安装 本次构建中,我们希望一个 VM 包含: 所有输入 | 程序/服务 --- | --- **说明** | **为什么?** nixpkgs (https://github.com/NixOS/nixpkgs) `nixos-unstable` | Nix 表达式的核心 monorepo | 拥有所有包管理器中最多的软件包 home-manager (https://nix-community.github.io/home-manager/) | 管理用户级程序与服务 | 常见项目,启用 home-manager 模块 Nixtamal (https://nixtamal.toast.al/) `next` | 带有不稳定特性的 Nix 输入固定器(大多数用户推荐 `stable`) | 展示 Darcs 拉取功能(当前其他输入固定器无法实现) LFM2.5-350M (https://huggingface.co/LiquidAI/LFM2.5-350M) | 紧凑的本地运行语言模型(本演示不想投入更多精力) | 只是一个文件,展示 `fresh-cmd` 以避免重新拉取 `GGUF` 文件 `fresh-cmd` zerostack (https://gi-dellav.github.io/zerostack/) | 极简的编码 agent TUI | 不在 Nixpkgs 中,包含一个 overlay 与一个 home-manager 模块,可用于「测试」LLM ## 设置 Nixtamal 首先选择一个 VCS(许多人可能会惊讶地发现*除了* Git 之外还有很多选项)。本项目我将使用 Pijul (https://pijul.org/)。 ``` $ pijul init Repository created at $XDG_PROJECTS_DIR/nixcfg $ cd nixcfg ``` 为了以一个不随时间和系统变化的基础开始,我们将使用镜像并将 Nixtamal@`nixos-unstable` 通道固定到一个特定的 Hydra 构建。 ``` $ nixtamal set-up --use-mirrors --nixpkgs-channel nixos-unstable Fetching fresh value for 「nixpkgs」 ... Prefetching input 「nixpkgs」 ... (this may take a while) Prefetched 「nixpkgs」. Making manifest file @ version:1.5.0 ``` ## 基本的 VM 配置 仅凭 `nixpkgs` 我们已经可以构建多种输出。当然包括实际的系统配置、ISO 镜像、netboot,还有各种云平台的多种镜像。让我们先在宿主机上为一个名为 `"vm"` 的新主机构建一个 QEMU 镜像。 ``` $ mkdir -p host/vm $ touch host/vm/{configuration,hardware-configuration,system}.nix ``` 先从作为入口点的 `system.nix` 开始: ``` # host/vm/system.nix { system ? builtins.currentSystem, specialArgs ? { }, }: let inputs = import ../../nix/tamal { }; in import "${inputs.nixpkgs}/nixos" { inherit system; configuration = { imports = [ ./configuration.nix ]; # To get a nice nixos-rebuild experience from anywhere # https://discourse.nixos.org/t/curious-about-system-nix/77977/9 nix.nixPath = [ "nixos-config=${./configuration.nix}" "nixos-system=${./system.nix}" "nixpkgs=/run/current-system/nixpkgs" ]; system.systemBuilderCommands = ''ln -s "${inputs.nixpkgs}" "$out/nixpkgs"''; }; } ``` 我们的 `configuration.nix` 是放置系统主要配置的地方。在本例中,我们将设置默认键盘布局与 shell,并允许 SSH 访问 VM。 ``` # host/vm/configuration.nix { pkgs, lib, ... }: { imports = [ ./hardware-configuration.nix ]; system.stateVersion = "26.11"; networking.hostName = "nixtamal-system"; # QWERTY is for non-nerds 😎 console.keyMap = "dvorak"; programs.fish.enable = true; users.users.toastal = { isNormalUser = true; createHome = true; extraGroups = [ "power" "wheel" ]; initialPassword = "pa$$word"; shell = pkgs.fish; openssh.authorizedKeys.keyFiles = [ (pkgs.fetchurl { url = "https://codeberg.org/toastal.keys"; hash = "sha256-DAir5keAHM13XDi2dKe5rS97DSeTSnqmOZODsTgUgvw="; }).outPath ]; }; services.openssh = { enable = true; openFirewall = true; settings = { PasswordAuthentication = false; KbdInteractiveAuthentication = false; PermitRootLogin = "no"; }; }; # Settings for the VM only virtualisation.vmVariant = { virtualisation.forwardPorts = [ { from = "host"; host.address = "127.0.0.1"; host.port = 2222; guest.port = 22; } ]; security.sudo.wheelNeedsPassword = false; }; } ``` 作为通常 NixOS 硬件扫描的替代,我们将放置虚拟化约束…… ``` # host/vm/hardware-configuration.nix { ... }: { virtualisation.vmVariant = { virtualisation = { memorySize = 8192; cores = 8; diskSize = 4096; }; }; } ``` 最后,我们构建并运行 VM: ``` $ nixos-rebuild build-vm --file host/vm building the system configuration... Done. The virtual machine can be started by running /nix/store/xb37cz4nafm82skxa3qa2gf7579zbgjd-nixos-vm/bin/run-nixtamal-system-vm $ /result/bin/run-nixtamal-system-vm Starting virtiofs daemons... $ kitty +kitten ssh -p 2222 toastal@localhost uname -a Linux nixtamal-system 6.18.59 x86_64 ``` 其输出与宿主机不同: ``` $ uname -srmn Linux manju 7.2.13 x86_64 ``` ## 添加 Home Manager 与 Nixtamal Home Manager 是 Nix 社区中常用的工具,它 > 是一个由 Nix 驱动的工具,用于可复现地管理用户主目录的内容。这包括程序、配置文件、环境变量,以及……任意文件。 在 Nixtamal 的 `next` 仓库中,现在已经有一个 Home Manager 模块。目前它只是启用了 `programs.nixtamal.\{enable,package\}`,但未来还会继续完善。现在通过拉取 Darcs 源仓库来测试它: 1. 为 Home Manager 与 Nixtamal 添加输入 2. 将它们连接为 `imports` 与 `overlays` 3. 从 `release` 蓝图创建 Nixtamal 演示项目 ### 添加新输入 ``` $ nixtamal tweak ``` 这会用 `$EDITOR`(你环境中配置的编辑器)打开我们的 `manifest.kdl`。 **注意** 我们*偏好*文本文件的原因是,随着时间推移,*押注于文本*总是最佳选择,它比记住并查询大量可能配置选项的 CLI 命令更容易调整。 ``` // ┏┓╻+╻ ╱┏┳┓┏┓┏┳┓┏┓╻ // ┃┃┃┃┗━┓╹┃╹┣┫┃┃┃┣┫┃ Read the manpage: // ╹┗┛╹╱ ╹ ╹ ╹╹╹ ╹╹╹┗┛ $ man nixtamal-manifest version "1.5.0" inputs { nixpkgs { archive { url "https://releases.nixos.org/nixpkgs/{{fresh_value}}/nixexprs.tar.zst" mirrors "https://mirrors.ustc.edu.cn/nix-channels/releases/nixpkgs-unstable%40{{fresh_value}}/nixexprs.tar.zst" "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixpkgs-unstable/nixexprs.tar.zst" extension tar.zst } hash algorithm=SHA-256 fresh-cmd { $ curl -Ls --http2 --compressed -o "/dev/null" -w "%{url_effective}" "https://channels.nixos.org/nixpkgs-unstable" | awk "-F/" "{print $NF}" } } home-manager { archive { url "https://github.com/nix-community/home-manager/archive/{{fresh_value}}.tar.gz" } fresh-cmd { $ git ls-remote --heads "https://github.com/nix-community/home-manager.git" --refs "refs/heads/master" | cut -f1 } } nixtamal { // IMPORTANT: other pinning tools can't support the Darcs kind! darcs { // next isn't mirrored, & we will be using the latest code repository "https://darcs.toastal.in.th/nixtamal/next/" } fresh-cmd { $ curl -sL --insecure "https://darcs.toastal.in.th/nixtamal/next/_darcs/weak_hash" } } } ``` 保存并退出后,需要锁定…… ``` $ nixtamal lock INPUTS ✓ nixpkgs (Archive) Fresh ✓ home-manager (Archive) Prefetched ✓ nixtamal (Darcs) Prefetched ``` ### 接线 Home Manager 为我们提供了一个模块。Nixtamal 为我们提供了一个 overlay 与一个 Home Manager 模块。 ``` # host/vm/system.nix { system ? builtins.currentSystem, specialArgs ? { }, }: let inputs = import ../../nix/tamal { }; in import "${inputs.nixpkgs}/nixos" { # OMITTING configuration the same as the prior section configuration = { imports = [ (import "${inputs.home-manager}/nixos") ]; nixpkgs.overlays = [ (import "${inputs.nixtamal}/nix/overlay") ]; home-manager = { useGlobalPkgs = true; useUserPackages = true; sharedModules = [ (_: { home.stateVersion = "26.11"; }) (import "${inputs.nixtamal}/nix/module/home-manager.nix") ]; }; }; } ``` Nixtamal 与 Home Manager 就绪后,我们可以通过新添加的模块启用 `nixtamal`: ``` # host/vm/configuration.nix { pkgs, lib, ... }: { # OMITTING configuration the same as the prior section home-manager.users.toastal = { programs = { nixtamal.enable = true; }; }; } ``` ### 测试 ``` $ nixos-rebuild build-vm --file host/vm building the system configuration... Done. The virtual machine can be started by running /nix/store/b8lzbmrhyxk9259mb8nmiyib3f2njlav-nixos-vm/bin/run-nixtamal-system-vm $ ./result/bin/run-nixtamal-system-vm Starting virtiofs daemons... ``` 在 Nixtamal + `system.nix` 驱动的 NixOS VM 内演示 Nixtamal。使用 Fish shell 调用 `nixtamal set-up ...` 命令,然后构建并运行基本的 release 蓝图。 ## 设置本地 LLM 我对 LLM 有一些复杂的感情(其中许多并非正面——特别是在社会、教育、资本与劳动等话题上),但既然我们无法把潘多拉放回盒子,朝更好方向迈出的一步就是至少自托管并在本地运行它们,以停止向**大型科技公司**分享数据。 为此我们需要一些新能力: 1. 为 zerostack 添加一个新的、已打补丁的输入 2. 添加一个带有 `fresh-cmd` 的文件输入,以避免对大文件(GGUF)进行不必要的重新拉取 3. 将 `inputs` 传递到系统配置中 4. 创建一个同时为系统与用户配置设置选项的新模块 ### 添加 zerostack 与一个小型 LLM ``` $ nixtamal tweak ``` ``` version "1.5.0" // OMITTING configuration the same as the prior sections inputs { zerostack { archive { url "https://github.com/gi-dellav/zerostack/archive/{{fresh_value}}.tar.gz" } // Our patch patches zerostack-hm // This gets the latest stable release (doesn't match: rc, beta, alpha) fresh-cmd { $ git ls-remote --tags "--sort=v:refname" "https://github.com/gi-dellav/zerostack.git" | grep -oP "refs/tags/\\Kv([0-9]+\\.)+[0-9]+(?:\\^{})?$" | tail -n1 | sed "s/\\^{}//" } } LFM2.5-350M-Q4_0 name=LFM2.5-350M-Q4_0.gguf { file { url "https://huggingface.co/LiquidAI/LFM2.5-350M-GGUF/resolve/{{fresh_value}}/LFM2.5-350M-Q4_0.gguf" } fresh-cmd { $ curl -fsL "https://huggingface.co/api/models/LiquidAI/LFM2.5-350M-GGUF/commits/main?path=LFM2.5-350M-Q4_0.gguf" | jq -r ".[0].id" } } } patches { // Open pull request to add a home-manager module zerostack-hm "https://patch-diff.githubusercontent.com/raw/gi-dellav/zerostack/pull/326.patch" } ``` ``` $ nixtamal lock INPUTS ✓ nixpkgs (Archive) Fresh ✓ LFM2.5-350M-Q4_0 (File) Prefetched ✓ home-manager (Archive) Fresh ✓ nixtamal (Darcs) Fresh ✓ zerostack (Archive) Patched PATCHES ✓ zerostack-hm Prefetched $ touch host/vm/local-llm-stuff.nix ``` ### 全部接线 添加 zerostack 的 overlay 与 Home Manager 模块只是开始,但我们也必须通过 `specialArgs` + `extraSpecialArgs` 将 `inputs` 传递下去(作为最终输出,这些变量没问题,只是不是首选方式),这样我们的新模块就可以在 llama-cpp 中引用我们的 `LFM2.5-350M-Q4_0` GGUF 文件。 ``` # host/vm/system.nix { system ? builtins.currentSystem, specialArgs ? { }, }: let inputs = import ../../nix/tamal { }; in import "${inputs.nixpkgs}/nixos" { # OMITTING configuration the same as the prior section specialArgs = specialArgs // { inherit inputs; }; configuration = { nixpkgs.overlays = [ (import "${inputs.zerostack}/nix/overlay") ]; home-manager = { extraSpecialArgs = { inherit inputs; }; sharedModules = [ (import "${inputs.zerostack}/nix/module/home-manager.nix") ]; }; }; } ``` 然后创建我们的新 `local-llm-stuff.nix` 文件…… ``` # host/vm/local-llm-stuff.nix { config, lib, inputs, ... }: let cfg = config.services.llama-cpp; in { services.llama-cpp = { enable = true; settings = { # ↓ GGUF goes here model = "${inputs."LFM2.5-350M-Q4_0"}"; alias = "lfm2.5-350m"; host = "127.0.0.1"; port = 8550; ctx-size = 8192; jinja = true; temp = 0.1; top-k = 50; repeat-penalty = 1.05; threads = 8; }; }; home-manager.sharedModules = [ # Use a module to set default values for all users (_: { programs.zerostack.settings = { api_keys.openai = lib.mkDefault "sk-no-key-required"; custom_providers.lfm-local = { model = cfg.settings.alias; provider_type = "openai"; base_url = "http://${cfg.s

相似文章

使用Nix构建系统软件

Lobsters Hottest

一篇博客文章,讨论Nix如何帮助解决构建系统软件时的依赖和可重现性问题,特别是针对像BPF和io_uring这样快速演进的子系统。

我喜欢的 NixOS 声明式安装方式

Michael Stapelberg

一份关于使用 nixos-anywhere 等工具通过网络声明式安装 NixOS 的指南,重点强调在版本控制下管理配置文件。

后现代构建系统

Lobsters Hottest

一篇博客文章,探讨理想中的'后现代'构建系统的设计,该系统优先考虑可信的增量构建、最大化计算复用和分布式构建,并以Nix作为参考。

不到100行代码实现nix-build

Lobsters Hottest

本文通过用不到100行Go代码重新实现nix-build,揭示了Nix构建过程,表明将派生转换为存储路径本质上就是一次执行。