OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 016

用 Flake 与 Home Manager 管理系统

把依赖入口、系统模块、用户配置和项目开发环境分清职责。

所在专题NixOS 系统基础与配置 ↗
约 17 分钟阅读开始阅读

核心概念速览#

NixOS 的配置由三个层次组成,理解它们的关系是一切的基础:

flake.nix          ← nix 命令直接读取,声明依赖,组装系统
    └── configuration.nix   ← 系统级配置(需要 sudo)
    └── home.nix            ← 用户级配置(home-manager 管理)

Nix 语言基础#

Nix 文件本质是一个返回值的表达式,最常见的返回值是 attribute set(键值对集合)。

Attribute Set(键值对集合)

{
  name = "Alice";
  age = 25;
  nested = {
    key = "value";
  };
}

函数

# 基本形式:参数: 返回值
x: x + 1

# 解构 attribute set 作为参数
{ name, age }: "Hello, ${name}, you are ${age}"

# 带默认值
{ name, age ? 0 }: "Hello ${name}"

# 捕获整个参数集合(@语法)
{ name, ... }@args: args

常用语法糖

# with 语句:展开 attribute set,避免重复前缀
with pkgs; [ git neovim curl ]
# 等价于
[ pkgs.git pkgs.neovim pkgs.curl ]

# inherit:继承上层作用域的变量
{ inherit inputs pkgs; }
# 等价于
{ inputs = inputs; pkgs = pkgs; }

# let...in:局部变量
let
  myVar = "hello";
in
"${myVar} world"

# 字符串插值
"Hello ${name}, you are on ${config.networking.hostName}"

列表

[ "item1" "item2" pkgs.git ]

flake.nix 详解#

flake.nix 不是函数,是一个有固定 schema 的 attribute set。Nix 工具认识这个结构,类似 npm 和 package.json 的关系。

{
  description = "My NixOS config";   # 可选,描述信息

  inputs = {
    # 声明外部依赖;默认作为 flake 处理,也可用 flake = false 引入普通源码
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";  # 让 HM 用同一份 nixpkgs,避免重复下载
    };
  };

  # outputs 的值是函数,参数是所有 inputs 求值后的结果
  outputs = { self, nixpkgs, home-manager, ... }@inputs: {

    # nixosConfigurations 是约定的 key,nixos-rebuild 会来这里找
    nixosConfigurations."workstation" = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";

      # specialArgs 把额外变量传递给所有子模块
      specialArgs = { inherit inputs; };

      modules = [
        ./configuration.nix
        home-manager.nixosModules.home-manager
        {
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.users.alice = import ./home.nix;
          home-manager.extraSpecialArgs = { inherit inputs; };
        }
      ];
    };
  };
}

inputs.X.follows 的作用

home-manager.inputs.nixpkgs.follows = "nixpkgs";

让 Home Manager 的 nixpkgs input 指向系统的同一锁定节点,减少重复输入。不同输入可能产生不同依赖集合,但并非必然下载两份或不兼容;Home Manager 分支也需要与系统版本匹配。

flake.lock

自动生成,记录每个 input 的精确 git commit hash。不要手动编辑。

  • nix flake update → 更新所有 inputs
  • nix flake update nixpkgs → 只更新 nixpkgs

configuration.nix 详解#

系统级配置,配置整台机器。修改后需要 sudo nixos-rebuild switch。

# 这是一个函数,参数由 NixOS 模块系统传入
{ config, lib, pkgs, inputs, ... }:

{
  imports = [
    ./hardware-configuration.nix   # 硬件配置,自动生成的
    ./modules/audio.nix            # 可以拆分成子模块
  ];

  # ── Boot ──────────────────────────────
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # ── Network ───────────────────────────
  networking.hostName = "workstation";
  networking.networkmanager.enable = true;

  # ── System packages ───────────────────
  environment.systemPackages = with pkgs; [
    wget curl git neovim
  ];

  # ── Users ─────────────────────────────
  # 本例把用户包统一交给 home.nix,用户身份和组由系统管理
  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ "wheel" "networkmanager" "video" ];
  };

  # ── 不要改这个值 ──────────────────────
  system.stateVersion = "25.11";
}

传入参数说明

参数 含义
pkgs nixpkgs 里所有包的集合
config 当前系统配置(可以引用其他选项)
lib nixpkgs 工具函数库
inputs flake inputs(需要 specialArgs 传入)

home.nix 详解#

用户级配置,由 home-manager 管理。配置的是某个用户的家目录、dotfiles、用户软件。

{ config, pkgs, inputs, ... }:

{
  imports = [
    ./modules/shell.nix
    ./modules/dev.nix
    inputs.some-flake.homeModules.default   # 接口示意,须在 inputs 中定义并按该项目实际输出替换
  ];

  # ── 必填 ──────────────────────────────
  home.username = "alice";
  home.homeDirectory = "/home/alice";
  home.stateVersion = "25.11";   # 不要随意改

  # ── 用户软件 ──────────────────────────
  home.packages = with pkgs; [
    htop btop obsidian
    ghostty kitty
  ];

  # ── 程序配置(带 dotfile 管理)─────────
  programs.git = {
    enable = true;
    settings = {
      user.name = "Example User";
      user.email = "user@example.com";
      init.defaultBranch = "main";
    };
  };

  programs.zsh = {
    enable = true;
    autosuggestion.enable = true;
    syntaxHighlighting.enable = true;
  };

  programs.home-manager.enable = true;
}

系统级 vs 用户级 边界#

判断依据:这个东西是"机器"需要的,还是"用户"需要的?

放 configuration.nix 放 home.nix
内核模块、驱动 终端模拟器、编辑器
系统服务(pipewire, bluetooth) shell 配置(zsh, bash)
防火墙、网络 dotfiles
字体(系统级) 用户软件包
users.users.X(账户及组) programs.git, programs.zsh 等
输入法框架(fcitx5) 输入法个性配置

统一包管理入口

# 有效的 NixOS 用户包选项,但在此组织方式中避免双入口
users.users.alice.packages = with pkgs; [ obsidian ];

# 使用 Home Manager 时可统一放在 home.nix
home.packages = with pkgs; [ obsidian ];

模块系统#

NixOS 和 home-manager 都使用同一套模块系统,理解它可以看懂任何配置。

# 一个模块的完整结构
{ config, lib, pkgs, ... }:

{
  # imports:引入其他模块
  imports = [ ./other-module.nix ];

  # options:声明这个模块提供的选项(写库/框架时用)
  options.myModule.enable = lib.mkOption {
    type = lib.types.bool;
    default = false;
  };

  # config:实际配置(条件激活时常用)
  config = lib.mkIf config.myModule.enable {
    environment.systemPackages = [ pkgs.hello ];
  };
}

大多数时候你只需要写顶层的配置,不需要 options 和 config 包装。


推荐目录结构#

/etc/nixos/
├── flake.nix                  # 入口,声明依赖
├── flake.lock                 # 自动生成,不要手动改
├── hardware-configuration.nix # 自动生成
├── configuration.nix          # 系统配置入口(精简,只 imports)
├── modules/                   # 系统级子模块
│   ├── locale.nix             # i18n / 时区 / 输入法
│   ├── graphics.nix           # GPU / Hyprland / display
│   ├── audio.nix              # pipewire / bluetooth
│   ├── network.nix            # network / firewall
│   └── nix-settings.nix      # substituters / nix-ld / flakes
└── home/
    ├── home.nix               # 用户配置入口
    ├── packages.nix           # home.packages 列表
    └── programs/
        ├── shell.nix          # zsh/bash/starship/zoxide
        ├── dev.nix            # git/zellij/direnv/neovim
        └── desktop.nix        # foot/ghostty/主题

常用命令速查#

更新锁文件和激活是两步;先审阅锁文件差异并构建,通过后再按需要执行 test、boot 或 switch。清理命令会移除回滚点。

# 重建并切换到新配置
sudo nixos-rebuild switch --flake /etc/nixos#workstation

# 临时激活新配置(会切换运行系统,重启回到启动默认项)
sudo nixos-rebuild test --flake /etc/nixos#workstation

# 回滚到上一代
sudo nixos-rebuild switch --rollback

# 列出所有系统代
nix-env --list-generations --profile /nix/var/nix/profiles/system

# 更新所有 flake inputs
sudo nix flake update --flake /etc/nixos

# 只更新某一个 input
sudo nix flake update nixpkgs --flake /etc/nixos

# 清理旧代(释放磁盘)
sudo nix-collect-garbage -d

# 查找包
nix search nixpkgs firefox

# 临时试用一个包(不安装)
nix shell nixpkgs#ripgrep

# 进入 devshell
nix develop

Per-project Dev Shell 模板#

用 Nix 提供项目工具和系统依赖;是否叠加 Python venv/uv 取决于项目,mkShell 本身不提供容器式进程隔离。

# 放在项目根目录的 flake.nix
{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs = { nixpkgs, ... }:
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in {
    devShells.x86_64-linux.default = pkgs.mkShell {
      packages = with pkgs; [
        # Python 科学计算(让 nix 管,不要 pip 装)
        (python3.withPackages (ps: [
          ps.numpy ps.matplotlib ps.scipy
        ]))
        uv   # 只用来管理纯 Python 包,不碰系统包
      ];

      shellHook = ''
        echo "Dev shell ready"
      '';
    };
  };
}
# 进入项目 dev shell
cd my-project
nix develop

# 也可以用 direnv 自动激活(需要 programs.direnv.enable = true)
echo "use flake" > .envrc
direnv allow

调试技巧#

依赖追踪需要两个 store 路径,第二个路径必须对应当前系统中实际使用的包版本;registry 的 nixpkgs 未必等于系统锁定输入。nix fmt 还要求 Flake 定义相应 formatter。

# 查看某个选项的文档
nixos-option services.pipewire.enable

# 查看某个包在哪里
nix eval nixpkgs#git.outPath

# 为什么某个包被引入(依赖追踪)
nix why-depends /run/current-system "$(nix eval --raw nixpkgs#gcc.outPath)"

# 检查 flake 结构
nix flake show /etc/nixos

# 格式化 nix 文件
nix fmt

参考资源#


参考与延伸

  1. NixOS 手册 ↗
  2. Nix 语言手册 ↗
  3. Home Manager 手册 ↗
  4. Nix:flake update ↗
  5. NixOS 与 Flakes 中文指南(原笔记引用) ↗
  6. Home Manager Git 模块 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

NixOS 系统基础与配置

  1. 01NixOS:声明式系统的基础
  2. 02安装 NixOS:介质、分区与初始配置
  3. 03NixOS 配置基础:选项与模块
  4. 04用 Flake 与 Home Manager 管理系统
  5. 05NixOS 命令行:新旧接口与日常维护
  6. 06NixOS 世代管理与垃圾回收
  7. 07Specialisation:一个世代的多个配置变体
  8. 08配置可重现,应用为什么还会“失忆”?
CONTINUE READING / 下一篇NixOS 命令行:新旧接口与日常维护

区分系统激活、用户 profile、临时环境和垃圾回收的命令边界。