使用 Nixtamal 构建 NixOS
摘要
一本食谱风格的指南,演示如何使用 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构建系统软件
一篇博客文章,讨论Nix如何帮助解决构建系统软件时的依赖和可重现性问题,特别是针对像BPF和io_uring这样快速演进的子系统。
我喜欢的 NixOS 声明式安装方式
一份关于使用 nixos-anywhere 等工具通过网络声明式安装 NixOS 的指南,重点强调在版本控制下管理配置文件。
Guix Nix 的怪异融合:在 Nix 中利用 Guix 派生
一项技术探索,展示了 Nix 如何构建 Guix 派生项,强调了共享底层“输入输出机”架构以及跨生态系统互操作的可能性。
后现代构建系统
一篇博客文章,探讨理想中的'后现代'构建系统的设计,该系统优先考虑可信的增量构建、最大化计算复用和分布式构建,并以Nix作为参考。
不到100行代码实现nix-build
本文通过用不到100行Go代码重新实现nix-build,揭示了Nix构建过程,表明将派生转换为存储路径本质上就是一次执行。