Chapter 06

NixOS 服务与模块系统

NixOS 模块系统是其架构的核心——理解 options/config 分离,实现可复用的系统配置抽象

NixOS 模块系统

NixOS 的整个配置系统——包括你的 configuration.nix——都是由模块组成的。每个模块是一个函数,接受 { config, pkgs, lib, ... } 并返回包含 options(声明配置接口)和 config(实现配置值)的 attrset。

模块系统工作原理 ────────────────────────────────────────────────── 模块 A 模块 B 模块 C options: options: options: services.nginx services.nginx ... .enable .virtualHosts config: config: if enable then for each vhost install nginx generate config ────────────────────────────────────────────────── │ │ └────────────────────┘ │ NixOS 模块系统合并所有模块的 options 声明和 config 值 │ 生成最终系统配置

模块结构剖析

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

{
  # ── options:声明这个模块提供的配置接口 ──
  options.my.service = {
    # mkEnableOption 生成标准的 enable 选项
    enable = lib.mkEnableOption "my custom service";

    port = lib.mkOption {
      type = lib.types.port;     # 类型验证
      default = 8080;
      description = "Listening port";
    };

    logLevel = lib.mkOption {
      type = lib.types.enum [ "debug" "info" "warn" "error" ];
      default = "info";
    };
  };

  # ── config:当 enable = true 时的实现 ──
  config = lib.mkIf config.my.service.enable {
    systemd.services.my-service = {
      description = "My Custom Service";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        ExecStart = "${pkgs.my-app}/bin/my-app --port ${toString config.my.service.port}";
        Restart = "always";
        User = "my-service";
      };
    };

    users.users.my-service = {
      isSystemUser = true;
      group = "my-service";
    };
    users.groups.my-service = {};
  };
}

常用 lib 函数

lib.mkIf
lib.mkIf condition value:条件性地设置配置项。当 condition 为 false 时,value 被忽略。用于"只有 enable = true 时才生成实际配置"。
lib.mkEnableOption
lib.mkEnableOption "description":生成标准的 enable 布尔选项,默认值为 false,描述为 "Whether to enable description"。
lib.mkOption
声明一个配置选项,指定类型、默认值、描述、示例等属性。支持的类型:bool、int、port、str、path、package、listOf、attrsOf、enum 等。
lib.mkDefault / lib.mkForce
控制选项值的优先级。mkDefault 设置低优先级默认值(可被其他模块覆盖),mkForce 设置高优先级值(强制覆盖其他设置)。
lib.mkMerge
lib.mkMerge [ config1 config2 ]:合并多个配置集合,等价于多个模块同时被激活。

Nginx 服务配置

{ config, pkgs, ... }:

{
  services.nginx = {
    enable = true;

    # 推荐的安全配置
    recommendedTlsSettings = true;
    recommendedOptimisation = true;
    recommendedGzipSettings = true;
    recommendedProxySettings = true;

    # 虚拟主机配置
    virtualHosts = {
      # 静态站点
      "example.com" = {
        enableACME = true;    # 自动申请 Let's Encrypt 证书
        forceSSL = true;
        root = "/var/www/example.com";
        locations."/".tryFiles = "$uri $uri/ /index.html";
      };

      # 反向代理到后端
      "api.example.com" = {
        enableACME = true;
        forceSSL = true;
        locations."/" = {
          proxyPass = "http://127.0.0.1:3000";
          proxyWebsockets = true;
          extraConfig = ''
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          '';
        };
      };
    };
  };

  # ACME(Let's Encrypt)配置
  security.acme = {
    acceptTerms = true;
    defaults.email = "admin@example.com";
  };

  # 开放 80/443 端口
  networking.firewall.allowedTCPPorts = [ 80 443 ];
}

PostgreSQL 服务配置

{
  services.postgresql = {
    enable = true;
    package = pkgs.postgresql_16;   # 指定版本

    # 认证配置(pg_hba.conf)
    authentication = pkgs.lib.mkOverride 10 ''
      local all all trust
      host all all 127.0.0.1/32 trust
      host all all ::1/128 trust
    '';

    # 初始化脚本(创建数据库和用户)
    initialScript = pkgs.writeText "pg-init" ''
      CREATE USER myapp WITH PASSWORD 'secret';
      CREATE DATABASE myapp OWNER myapp;
      GRANT ALL PRIVILEGES ON DATABASE myapp TO myapp;
    '';

    # postgresql.conf 设置
    settings = {
      max_connections = 100;
      shared_buffers = "256MB";
      effective_cache_size = "1GB";
    };
  };
}

Docker 与虚拟化

{
  # 启用 Docker
  virtualisation.docker = {
    enable = true;
    autoPrune.enable = true;    # 定期清理未使用镜像
    daemon.settings = {
      log-driver = "json-file";
      log-opts = { max-size = "10m"; max-file = "3"; };
    };
  };

  # 启用 Podman(rootless 容器,Docker 替代)
  virtualisation.podman = {
    enable = true;
    dockerCompat = true;   # 提供 docker 命令兼容层
  };

  # 将用户加入 docker 组
  users.users.alice.extraGroups = [ "docker" ];
}

自定义 Systemd 服务

{ pkgs, ... }:

{
  systemd.services.my-webapp = {
    description = "My Web Application";
    wantedBy = [ "multi-user.target" ];
    after = [ "network.target" "postgresql.service" ];
    requires = [ "postgresql.service" ];

    environment = {
      NODE_ENV = "production";
      PORT = "3000";
      # 注意:敏感信息应使用 sops-nix(第10章)
    };

    serviceConfig = {
      ExecStart = "${pkgs.nodejs_20}/bin/node /var/www/app/server.js";
      WorkingDirectory = "/var/www/app";
      User = "webapp";
      Group = "webapp";
      Restart = "on-failure";
      RestartSec = "5s";
      # 安全加固
      PrivateTmp = true;
      ProtectSystem = "strict";
      NoNewPrivileges = true;
    };
  };

  # 创建服务用户
  users.users.webapp = {
    isSystemUser = true;
    group = "webapp";
    home = "/var/www/app";
    createHome = true;
  };
  users.groups.webapp = {};
}

完整 Web 服务实战

将 Nginx + PostgreSQL + 自定义 App 组合部署:

# /etc/nixos/configuration.nix 片段
{ config, pkgs, ... }:

{
  # 包含其他模块文件
  imports = [
    ./hardware-configuration.nix
    ./services/webapp.nix     # 自定义模块
  ];

  # 启用自定义模块
  my.webapp = {
    enable = true;
    domain = "mysite.com";
    port = 3000;
  };

  # Nginx
  services.nginx.enable = true;

  # PostgreSQL
  services.postgresql.enable = true;

  # 防火墙
  networking.firewall.allowedTCPPorts = [ 22 80 443 ];
}

# /etc/nixos/services/webapp.nix —— 自定义模块
{ config, pkgs, lib, ... }:

let
  cfg = config.my.webapp;
in {
  options.my.webapp = {
    enable = lib.mkEnableOption "my web application";
    domain = lib.mkOption { type = lib.types.str; };
    port = lib.mkOption { type = lib.types.port; default = 3000; };
  };

  config = lib.mkIf cfg.enable {
    # Nginx 配置
    services.nginx.virtualHosts.${cfg.domain} = {
      enableACME = true;
      forceSSL = true;
      locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
    };

    # 应用服务
    systemd.services.webapp = {
      wantedBy = [ "multi-user.target" ];
      after = [ "postgresql.service" ];
      serviceConfig.ExecStart = "...";
    };
  };
}
本章小结

NixOS 模块系统通过 options/config 分离实现了声明式的服务配置:options 定义配置接口,config 实现实际效果,lib.mkIf 实现条件激活。Nixpkgs 中有数百个内置服务模块(Nginx、PostgreSQL、Redis、Docker 等),配置只需几行 Nix 代码,且所有配置都是类型安全的,错误的配置值在 nixos-rebuild 时就会报错,而不是运行时才发现。