NixOS 系统教程 · 从一行配置到多主机架构
NIXOS 入门系统教程

从一行配置,到一个操作系统

会一点 Linux,但看见 { config, pkgs, lib, ... }: 就犯迷糊?本文用三层心智模型 + 三张架构图,把 Flake、模块系统与构建机制一次讲清。

01 · 地基
Nix 语言速览
属性集、函数、惰性求值——看懂示例所需的最小语法
02 · 配置层
模块系统
imports / options / config / lib / pkgs 如何协作出系统配置
03 · 组织层
Flake 与多主机
依赖锁定、hosts + modules 架构,一个 Flake 管多台机器
NixOS 模块系统全流程信息图
模块系统全流程信息图(原图)—— 六个名字与求值流程的总览
00

三层心智模型

先建立整体认识:NixOS 体系由三层叠加而成,任何细节都能归位到某一层。

一句话记住整个体系 Flake 决定“项目怎么组织、依赖怎么锁”;模块系统决定“每台主机内部怎么配”;Nix 负责“把配置变成可回滚的系统代际”。 推荐架构:一个 Flake 管理多台主机,每台主机组合多个可复用模块。
NixOS 三层心智模型:Flake 组织层 · 模块系统配置层 · 构建激活层 NixOS 三层心智模型 “一个 Flake 管理多台主机,每台主机组合多个可复用模块,Nix 把配置变成可回滚的系统代际” 第一层 · Flake —— 项目组织层(“项目怎么组织、依赖怎么锁”) inputs 声明外部依赖来源 github:NixOS/nixpkgs 决定“用哪个 nixpkgs” outputs 定义项目输出 nixosConfigurations · packages devShells · checks 决定“构建哪些主机” flake.lock 锁定解析到的修订 revision · narHash 防依赖漂移,但不保证绝对一致 nixosSystem { system, modules } 第二层 · NixOS 模块系统 —— 配置层(“一台操作系统怎么被配置出来”) imports 组合模块收集哪些模块组装组件 options 声明合法接口类型 · 默认值Schema config 提交定义读取合并结果双重身份 lib mkIf / mkDefaultmkForce / mkOption辅助规则库 pkgs 引用软件包git · curl · nginx软件依赖目录 求值 · 类型检查 · 合并 第三层 · Nix 构建与激活 —— 两篇共同背景(“配置怎么变成可运行系统”) 求值合并后的配置 Nix Store 构建产物 System Generation 代际(可回滚) ◆ 蓝=Flake 组织层 ◆ 紫=模块系统配置层 ◆ 绿=构建激活层 ◆ 桥=nixosSystem
图 0 · 三层心智模型:蓝色管组织、紫色管配置、绿色管构建,nixosSystem 是衔接桥梁

三层各自回答什么问题

层名称回答的问题主要概念
第一层Flake(组织层)项目怎么组织、依赖怎么锁inputs · outputs · flake.lock
第二层模块系统(配置层)一台操作系统怎么配imports · options · config · lib · pkgs
第三层Nix 构建系统配置怎么变成可运行系统Nix Store · Generation · 激活

三层的衔接桥梁是 nixosSystem { system, modules }——它接收 Flake 的输入,把模块列表交给模块系统求值。

01

Nix 语言一分钟速览

看懂后续所有示例所需的最小语法集。如果已经熟悉,可直接跳到下一章。

语法写法说明
属性集{ a = 1; b = "x"; }类似对象 / 字典
访问属性config.my.ai.enable点号取值
函数x: x + 10冒号前是参数
多参函数{ config, pkgs, lib, ... }: { ... }参数是一个属性集,... 接收额外参数
绑定let cfg = config.my.ai; in ...局部命名
引入文件import ./network.nix求值表达式机制
列表[ pkgs.git pkgs.curl ]空格分隔
惰性求值—只算被需要到的东西,允许自引用
最容易混淆的一对 Nix 语言的 import ≠ 模块系统的 imports。前者是表达式求值机制,后者是收集模块的选项。

import vs imports —— 语言层与配置层的区别

它俩属于两个不同的“世界”:import 是 Nix 语言的求值机制(把文件变成“一个值”);imports 是 NixOS 模块系统的收集选项(把模块“拉进队伍”一起合并)。

import 与 imports 的区别:语言层求值机制 vs 模块系统收集选项 import vs imports:语言层 与 配置层的区别 同名不同物 —— 一个产生“值”,一个收集“模块” import —— Nix 语言 · 求值机制 把 .nix 文件求值成“一个值”(属性集 / 函数 / 数字…) data.nix { name = "nginx"; port = 80; } import ./data.nix 得到 → 一个值 { name = "nginx"; port = 80; } 可继续取属性:cfg.name → "nginx" 若文件是函数,得到的是函数本身(未调用) 类比:require / import 文件,执行完拿回导出值 位置:任意表达式里(let、属性值…) imports —— 模块系统 · 收集选项 声明哪些模块参与求值,交给模块系统统一合并 模块主体 imports = [ ./network.nix ./security.nix ]; 模块系统收集 + 合并 得到 → 一份最终 config 所有模块的 options / config 定义 合并成最终配置 配合优先级、条件(mkIf / mkDefault…) 类比:组装组件清单,把模块拉进队伍 位置:模块主体的顶层字段 两者常配合出现 let base = import ./base.nix; # ① 求值文件 → 得到模块 imports = [ base ./network.nix ]; # ② 收集模块 → 参与合并
图 1a · import 求值出“值”,imports 收集“模块”——两者常在同一配置里配合出现

① import:Nix 语言的表达式求值机制

任何 .nix 文件都可以被 import:读取文件 → 当 Nix 表达式求值 → 得到一个值(属性集、函数、数字…)。

# data.nix —— 一个纯数据文件
{ name = "nginx"; port = 80; }
# 使用处
let cfg = import ./data.nix;   # import 求值 → 得到属性集 { name = "nginx"; port = 80; }
in cfg.name                    # → "nginx"

如果文件里写的是函数,import 得到的还是那个函数本身(未调用):

# module-file.nix —— 一个模块形状的函数
{ config, pkgs, ... }: { services.openssh.enable = true; }

# 使用处
let m = import ./module-file.nix;   # m = 那个函数,还没执行

类比:像 JavaScript 的 require / Python 的 import,执行完那个文件,把它的导出值拿回来。

② imports:NixOS 模块系统的收集选项

写在模块主体的顶层,声明“这次配置求值要合并哪些模块”。不负责求值,只负责收集,合并交给模块系统。

{ ... }:
{
  imports = [ ./network.nix ./security.nix ];
  services.openssh.enable = true;
}

③ 关键区别

维度importimports
所属层级Nix 语言(通用机制)NixOS 模块系统(专用选项)
本质求值表达式声明性配置字段
作用把文件求值成“一个值”收集模块参与合并
有返回值吗有(属性集/函数/…)无,只是声明集合
出现位置任意表达式里(let、属性值…)模块主体顶层字段
类比“加载并执行文件”“组装组件清单”

④ 它们其实经常配合出现

{ config, pkgs, lib, ... }:
let
  base = import ./base.nix;          # ① Nix import:把文件求值成模块函数
in
{
  imports = [ base ./network.nix ];  # ② 模块 imports:把模块纳入合并
}

甚至可以写成一行:imports = [ (import ./base.nix) ]; —— 括号内是 import 求值,括号外是 imports 收集。

终极记忆法import 回答“这个文件求值完是什么(值)”;imports 回答“这次系统配置要合并哪些模块(集合)”。
02

Flake —— 项目组织层

项目怎么组织、依赖怎么锁、要构建哪些主机——这些都由 Flake 在“比系统配置更高一层”回答。

NixOS Flake 多主机架构图
Flake 多主机架构信息图(原图)—— 输入输出与多主机输出概览
一句话理解 Flake Flake 是 Nix 项目的标准化组织机制:声明依赖输入(Inputs)、计算项目输出(Outputs),从而管理软件、开发环境和操作系统配置。

传统 Linux vs NixOS vs Flake

传统 Linux 靠命令操作:

# 告诉机器“怎么做”
apt install nginx
systemctl enable nginx
systemctl start nginx

NixOS 用配置描述目标状态:

services.nginx.enable = true;

而 Flake 在更高一层,回答:用哪个版本的 Nixpkgs?构建哪些机器的配置?每台机器用哪些模块?依赖版本如何锁定?

Flake 与 NixOS Modules 的区别

对比FlakeNixOS Modules
核心职责项目依赖与输出管理操作系统配置管理
主要文件flake.nixconfiguration.nix、modules/*.nix
核心概念Inputs · Outputs · LockImports · Options · Config
软件依赖引入 Nixpkgs 等输入通过 pkgs 引用软件包
多机管理定义多个系统输出组合各台机器的配置
是否独立使用可以可以
核心理解Flake 是项目组织层,Modules 是系统配置层。

核心组成

一个最基本的 flake.nix:

{
  description = "My NixOS System";

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

  outputs = { self, nixpkgs, ... }: {
    nixosConfigurations.myserver =
      nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./configuration.nix ];
      };
  };
}
description
项目说明。只描述,不改变系统状态。
inputs
声明外部依赖来源,决定“用哪个 nixpkgs 分支”。
outputs
定义项目输出,决定“构建哪些主机 / 包 / 环境”。
self
指向本 Flake 自身,可在 outputs 内引用自己的模块。
flake.lock
锁定解析到的修订与哈希,防止依赖漂移。
关于版本分支示例中的 nixos-25.11 为教学占位,实际部署请选择当前受支持的 NixOS 版本分支。

函数式机制:Outputs = F(Inputs)

outputs 本质是一个接收输入、返回属性集的 Nix 函数:

输入:self + nixpkgs + 其他 Flake 依赖
        ↓
   outputs 函数计算
        ↓
输出:NixOS 系统配置 · 软件包 · 开发环境 · 项目检查

常见输出类型

输出名称用途
nixosConfigurations定义 NixOS 系统配置
packages定义可构建的软件包
devShells定义开发环境
apps定义可通过 Nix 运行的应用
checks定义项目检查
formatter定义代码格式化工具

一个 Flake 可以同时提供服务器配置、桌面配置、Python 开发环境、自定义软件包与项目检查——这正是 Flake ≠ configuration.nix 的原因。

依赖锁定机制

flake.nix → 依赖解析 → flake.lock → 后续使用锁定的依赖

即使远程分支后来更新,持有原 flake.lock 的项目仍引用先前锁定的版本,减少多台机器之间的依赖漂移。

边界锁文件固定的是依赖解析结果,不代表构建结果绝对一致。系统架构、非确定性构建因素、运行环境仍可能造成差异。

常用命令

命令作用
nix flake metadata查看 Flake 信息
nix flake show查看 Flake 输出
nix flake check检查 Flake 输出是否满足要求
nix flake update更新锁定依赖
03

NixOS 模块系统 —— 配置层

一台操作系统具体怎么配?五个名字各司其职,通过求值、合并、构建变成可运行系统。

传统 vs 声明式

传统 Linux:告诉机器“怎么做”——一串操作命令,逐步修改当前系统。
NixOS:告诉系统“想要什么”——

services.nginx.enable = true;

模块系统读取配置定义,计算所需的服务、软件与文件,经 Nix 构建生成系统代际并激活。它不是 Linux 内核直接解析 Nix 配置,也不只是把一行状态翻译成三行 Shell 命令。

五个名字的职责

先看最常见的模块外形——注意第一行是函数参数列表,第二个大括号开始才是模块主体:

{ config, pkgs, lib, ... }:
{
  imports = [ ./network.nix ];
  services.openssh.enable = true;
}
名字类别作用记忆法
imports模块组织把其他模块纳入同一次求值组装组件
options接口声明声明合法选项、类型、默认值Schema
config配置定义与读取提交定义 / 读取合并结果双重身份
pkgs软件依赖引用 Nixpkgs 软件包产物软件依赖目录
lib辅助规则mkIf/mkDefault/mkForce 等工具框架工具库
NixOS 模块系统:五个名字 + 求值五环节 + config 双重身份 NixOS 模块系统:五个名字如何协作 左边是“角色”,右边是“求值的五个环节”——逻辑拆分,非逐行执行(Nix 惰性求值) ① 五个名字的角色 imports —— 组合模块 把其他模块纳入同一次求值;不是脚本,不是文本粘贴 类比:组装组件 options —— 声明接口 声明合法选项、类型、默认值;不负责安装或启动 类比:接口定义 / Schema config —— 定义 + 读取 模块主体里提交定义;函数参数里读取合并结果 双重身份,见下方③ pkgs —— 软件依赖 引用 Nixpkgs 中的构建产物,pkgs.git ≠ apt install git 类比:统一软件依赖目录 lib —— 辅助规则 mkEnableOption · mkIf · mkDefault · mkForce 类比:框架工具库 ② 求值五环节 1. 收集 imports 确定哪些模块参与 2. 声明 options 知道哪些选项合法、什么类型 3. 定义 config 各模块提供具体值与条件定义 4. 合并 lib 与模块系统:类型、条件、优先级检查 5. 构建 pkgs 与 Nix:引用产物,生成系统代际 ③ config 的双重身份(最常见混淆点) 模块主体里的 config = {…} 是“我提供哪些定义”;函数参数里的 { config, … }: 是“系统最终算出什么”。两者经递归求值关联,不是从上到下赋值执行。
图 1 · 五个名字如何协作:角色在左、五环节在右、config 双重身份在底

真实案例:一行开关启用 Ollama

目标:主配置只写一行 my.ai.enable = true;,就启用 Ollama 服务。

项目结构:

/etc/nixos/
├── configuration.nix
└── modules/
    └── ai.nix

自定义模块 modules/ai.nix:

{ config, pkgs, lib, ... }:

let
  cfg = config.my.ai;
in
{
  # ① 声明自定义配置接口
  options.my.ai.enable =
    lib.mkEnableOption "AI service";

  # ② 当开关开启时,提供以下配置定义
  config = lib.mkIf cfg.enable {
    services.ollama.enable = true;
    environment.systemPackages = [
      pkgs.curl
    ];
  };
}

主配置 configuration.nix(片段,非整份替换):

{ ... }:
{
  imports = [
    ./modules/ai.nix
  ];

  my.ai.enable = true;
}
序号动作参与者
1options 声明 my.ai.enable 为合法布尔开关lib
2config.my.ai 读取开关合并结果,取别名 cfgconfig
3lib.mkIf 在开关为真时提供定义lib
4services.ollama.enable = true 要求启用 Ollamaconfig
5pkgs.curl 把 curl 纳入系统软件包pkgs
关闭开关 ≠ 强制禁用改成 my.ai.enable = false 后,本模块不再提供 Ollama 与 curl 的定义;但如果其他模块仍启用,它们依然可能存在。“不提供启用定义”不等于“强制所有模块禁用”。

为什么 config 看起来会引用自己?

{ config, lib, ... }:
{
  options.my.demo.enable = lib.mkEnableOption "demo";

  config = lib.mkIf config.my.demo.enable {
    services.openssh.enable = true;
  };
}

表面看计算 config 需要先知道 config.my.demo.enable。但模块系统先收集选项定义,再按需计算值;lib.mkIf 把条件保留在定义层面,避免直接用 Nix if 控制模块结构时的无限递归。

惰性 ≠ 循环依赖都合法真正无法解析的递归仍会报错。

合并、条件与优先级

列表类型可以合并:两个模块分别定义 allowedTCPPorts = [ 80 ] 与 [ 443 ],合并为 [ 80 443 ]。不是所有类型都这样合并,普通字符串选项同优先级不同定义通常会冲突。

默认值与覆盖:

# 基础模块
{ lib, ... }: { services.openssh.enable = lib.mkDefault true; }

# 机器专用模块
{ ... }: { services.openssh.enable = false; }

最终值为 false:普通定义优先于 mkDefault。

lib.mkIf
有条件地提供定义。记忆法:满足条件才定义。
lib.mkDefault
提供较低优先级定义。记忆法:给默认建议。
lib.mkForce
提供较高优先级定义。记忆法:强制覆盖常规定义。
04

协同工作流(Flake + Modules)

Flake 与模块系统通过 nixosSystem 桥接,形成“依赖 → 配置 → 构建 → 激活”的完整链路。

整体链路

flake.nix (inputs → outputs)
        │
        ▼
nixosSystem { system, modules }   ← 桥梁
        │
        ▼
NixOS Modules (imports / options / config / pkgs / lib)
        │
        ▼
模块求值与合并(类型检查、优先级、默认值)
        │
        ▼
系统构建与激活(Nix Store → System Generation)

nixosSystem 的作用

nixpkgs.lib.nixosSystem {
  system = "x86_64-linux";
  modules = [ ./configuration.nix ];
}

它是 Nixpkgs 提供的函数,根据目标平台和模块列表建立一个 NixOS 系统配置;modules 参数指定参与求值的模块。

模块如何接收参数?

{ config, pkgs, lib, ... }:
{
  services.openssh.enable = true;
  environment.systemPackages = [ pkgs.git ];
}
参数作用
config访问模块系统合并后的配置
pkgs访问软件包集合
lib使用 Nix 辅助函数
...接收其他模块参数
注意这些函数参数由NixOS 模块系统提供,不是用户必须在 flake.nix 中手动逐个传递。
05

多主机架构实战

当系统涉及多台电脑、服务器、Docker、AI 服务时,用 hosts + modules 结构把差异与复用分开。

为什么不建议把所有配置写进 flake.nix?

当系统涉及多台电脑、多台服务器、Docker 容器、AI 模型服务、网络与安全策略、开发环境时,单文件会导致:

更合理的分工Flake 负责依赖与输出,Hosts 负责机器差异,Modules 负责功能复用。

推荐项目结构

nixos-config/
│
├── flake.nix
├── flake.lock
├── README.md
│
├── hosts/
│   ├── desktop/          # default.nix + hardware-configuration.nix
│   ├── server/           # default.nix + hardware-configuration.nix
│   └── ai-node/          # default.nix + hardware-configuration.nix
│
└── modules/
    ├── base.nix
    ├── users.nix
    ├── network.nix
    ├── security.nix
    ├── docker.nix
    ├── ollama.nix
    └── development.nix
NixOS 多主机架构:Flake · hosts · modules 推荐的多主机架构:一个 Flake · 多个 Hosts · 复用 Modules Flake 管依赖与输出 · Hosts 管机器差异 · Modules 管功能复用 flake.nix inputs.nixpkgs.url = "github:NixOS/nixpkgs/…" outputs → nixosConfigurations.desktop / .server / .ai-node flake.lock nixosSystem { system, modules } hosts/ —— 每台机器的差异 desktop/ default.nix imports 引用基础 + 桌面专属模块 hardware-configuration.nix 磁盘、文件系统(机器专属) hostName = "desktop" server/ default.nix 基础 + 网络 + 安全 + Docker hardware-configuration.nix 磁盘、文件系统(机器专属) hostName = "server" ai-node/ default.nix 基础 + 网络 + 安全 + Docker + Ollama hardware-configuration.nix 磁盘、文件系统(机器专属) hostName = "ai-node" imports 组合复用 modules/ —— 可复用功能模块(一个功能一份) base.nix 系统基础 network.nix 网络与防火墙 security.nix 安全策略 docker.nix virtualisation.docker ollama.nix services.ollama development.nix git · curl · htop 同样一套模块可被不同主机组合 —— 服务器只要 Docker,AI 节点要 Docker + Ollama nix flake check → sudo nixos-rebuild build --flake .#ai-node sudo nixos-rebuild switch --flake .#ai-node
图 2 · 一个 Flake 管三台主机:flake.nix 定义输出,hosts 保存差异,modules 提供复用
目录或文件作用
flake.nix定义依赖与系统输出
flake.lock锁定依赖版本
hosts/保存每台机器的配置
modules/保存可复用功能模块
hardware-configuration.nix保存硬件、文件系统等机器相关配置
重要提醒硬件配置应以目标机器实际生成或确认的内容为准,不应直接复制其他机器的磁盘标识、文件系统设置。

编写 flake.nix(三主机)

{
  description = "Multi-host NixOS";

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

  outputs = { nixpkgs, ... }: {
    nixosConfigurations = {
      desktop = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./hosts/desktop/default.nix ];
      };
      server = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./hosts/server/default.nix ];
      };
      ai-node = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./hosts/ai-node/default.nix ];
      };
    };
  };
}

三台主机共用同一套 Nixpkgs 依赖,同时组合不同的系统模块。

编写可复用模块

# modules/development.nix
{ pkgs, ... }:
{
  environment.systemPackages = [
    pkgs.git
    pkgs.curl
    pkgs.htop
  ];
}

# modules/docker.nix
{ ... }:
{
  virtualisation.docker.enable = true;
}

# modules/ollama.nix
{ ... }:
{
  services.ollama.enable = true;
}

编写主机配置

# hosts/server/default.nix —— 服务器:只有 Docker
{ ... }:
{
  imports = [
    ./hardware-configuration.nix
    ../../modules/base.nix
    ../../modules/network.nix
    ../../modules/security.nix
    ../../modules/docker.nix
  ];

  networking.hostName = "server";
}

# hosts/ai-node/default.nix —— AI 节点:Docker + Ollama
{ ... }:
{
  imports = [
    ./hardware-configuration.nix
    ../../modules/base.nix
    ../../modules/network.nix
    ../../modules/security.nix
    ../../modules/docker.nix
    ../../modules/ollama.nix
  ];

  networking.hostName = "ai-node";
}

这样 AI 节点拥有 Docker + Ollama,普通服务器只需要 Docker。

说明base.nix、network.nix、security.nix 需自行实现;这是架构示例,不是可直接替换现有系统的完整安装配置。

构建与部署

命令作用
nix flake check检查 Flake 输出是否满足要求
sudo nixos-rebuild build --flake .#ai-node构建但不切换当前系统
sudo nixos-rebuild switch --flake .#ai-node构建并激活新系统代际
操作顺序建议更新依赖 → 先检查变更 → 构建测试 → 再 switch。flake check 与 build 成功不代表网络、驱动、硬件、服务一定能正常运行,系统激活后仍需测试。
06

常见误区汇总

两篇教程的误区去重合并成九条,每条都是一次“理解偏差 → 正确认识”的纠正。

误区 1:NixOS 是 Linux 内核读取配置后执行命令
不是。内核不负责解释 NixOS 模块;模块求值、构建和激活由用户空间的 Nix/NixOS 工具完成。
误区 2:imports 等于 Nix 语言的 import
不是。imports 是模块系统用来收集模块的选项;Nix 的 import 是表达式求值机制。两者可以配合,但含义不同。
误区 3:options 一写,服务就启动
不是。options 只声明接口,必须有对应配置定义以及构建、激活过程。
误区 4:config 是一个普通全局变量
不是。它是模块系统提供的、由所有模块定义合并计算得到的配置上下文。
误区 5:services.xxx.enable = true 意味着服务永远自动恢复
不一定。NixOS 声明目标配置,不等于 Kubernetes 那样持续执行状态协调循环;具体重启行为由 systemd 服务策略决定。
误区 6:NixOS 可以回滚一切
不对。系统代际可以回滚,但数据库内容、用户文件等可变数据不会自动跟着回滚。
误区 7:Flake 就是 NixOS 的主配置文件
不准确。Flake 是 Nix 项目的输入输出组织机制,NixOS 模块负责定义操作系统配置。
误区 8:所有配置都必须写在 flake.nix / inputs 就是要安装的软件
错误。Flake 可引用独立的模块文件;inputs 只声明依赖来源,装什么由输出定义决定。
误区 9:flake.lock 能保证绝对一致 / 构建成功代表系统正常
不成立。锁文件只固定依赖解析结果;构建成功仍需实测网络、驱动、硬件与服务是否正常运行。
07

进阶方向 —— 可视化配置平台

若要开发一个通过网页配置 NixOS 的系统,可把 Flake 与模块库结合成五段流水线。

Web 可视化管理界面(选择主机 / 软件 / 网络 / AI 服务)
        │
        ▼
结构化配置模型(JSON / Schema:参数校验、默认值、约束)
        │
        ▼
Nix 配置生成引擎(生成主机配置、选择模块、生成 Flake 输出)
        │
        ▼
NixOS 配置仓库(flake.nix / hosts / modules)
        │
        ▼
验证、构建与部署(nix flake check / nixos-rebuild build / switch)

为什么复用 NixOS Modules? 模块系统已提供配置选项、类型定义、默认值、配置合并、条件定义、选项描述、软件包依赖、模块复用——可视化工具不必重新发明一套系统配置语言。

推荐路线提取 NixOS 选项元数据 → 映射为表单 Schema → 用户配置参数 → 生成 Nix 模块定义 → 验证和构建。
边界NixOS 选项类型并不能在所有情况下无损转换成 JSON Schema。函数、子模块、复杂类型与动态约束仍需专门处理。
附录

知识速查

概念、命令、心智模型三张速查表,学完随时回来翻。

概念速查表

你想问的问题找谁
其他配置模块从哪里来?imports
这个配置项是否存在、是什么类型?options
这个配置项设成什么值?模块主体的 config / 简写定义
最终合并出来的值是什么?函数参数 config
我要引用 Git、curl、Nginx 软件包。pkgs
我要定义开关、条件、默认值、覆盖规则。lib
项目用哪个 nixpkgs、构建哪些主机?Flake 的 inputs / outputs
依赖版本怎么固定?flake.lock

命令速查表

场景命令
只求值构建,不切换sudo nixos-rebuild build
构建并激活sudo nixos-rebuild switch
指定 Flake 与主机sudo nixos-rebuild switch --flake /etc/nixos#myserver
构建指定主机sudo nixos-rebuild build --flake .#ai-node
查看 Flake 信息nix flake metadata
查看 Flake 输出nix flake show
检查 Flakenix flake check
更新锁定依赖nix flake update

最终心智模型

模块 = 声明接口 + 提供配置定义 多个模块 --imports--> 模块集合 │ options 约束合法性 │ config 汇集定义 │ lib 辅助条件与优先级 │ 求值得到最终 config │ 引用 pkgs 中的软件包 │ 构建并激活系统
记住这一句就够了 imports 负责组合,options 负责定义接口,config 负责定义和读取配置,pkgs 负责软件依赖,lib 负责辅助规则。它们共同服务于 NixOS 的模块求值,而不是一条条 Shell 命令的包装。

再加一层:Flake 决定“用哪个 nixpkgs、构建哪些主机”,模块系统决定“每台主机内部怎么配”。

延伸阅读(官方)

附注Ollama 的实际模型下载、硬件加速与资源占用需单独配置;示例仅演示模块协同,不代表已完成生产级 AI 服务部署。硬件配置与系统版本分支请以目标机器实际为准。