PHASE 3 · 从零构建路由器

3.2NixOS 声明式路由器

把整台机器写成一份声明

3.1 结尾留了个问题:那三十行命令重启就没了。这章给答案——用 NixOS 把整台路由器写成一份声明。声明式的意思是:你不再"敲命令让机器变成路由器",而是"写下这台机器应该是什么样"(有哪些接口、哪条 NAT、哪个 dnsmasq),系统负责让实际状态永远等于你的声明。3.1 那五步的"意图",会变成一份能进 Git、能回滚、能在任何机器上复现的配置文件。你熟 NixOS,这章正对路子——把学过的路由器知识,用声明式的方式固化下来。

一句话定义

声明式路由器 = 把 3.1 那五步(接口、转发、NAT、防火墙、dnsmasq)写成一份 NixOS 配置声明;nixos-rebuild 后,系统保证实际状态等于声明——重启不丢、换机可复现、改坏能回滚。

1命令式 vs 声明式

3.1 是命令式(告诉机器"做这些动作");NixOS 是声明式(告诉机器"成为这个样子")——区别在于谁来保证最终状态。

命令式(3.1)
告诉机器"做这些动作"
ip addr add …
sysctl -w …
nft add rule …
dnsmasq …
⚡ 运行时状态 · 重启即失 · 易漂移
声明式(NixOS)
告诉机器"成为这个样子"
networking.nat.enable = true;
boot.kernel.sysctl… = 1;
services.dnsmasq.enable = true;
📄 系统定义 · 重启仍在 · 可复现可回滚

关键差异:命令式里"配置"= 你敲过的命令历史(散落、易失);声明式里"配置"= 一份完整描述最终状态的文件(单一、持久、就是真相)。这正好解决 2.2 提过的"配置状态 vs 运行状态"分裂——声明式里,配置状态就是唯一的真相,运行状态由它推导

你熟的类比

命令式像"手动一步步操作数据库",声明式像"写一份 schema,让工具把数据库变成 schema 描述的样子"。你用过的很多现代工具都是这个思路(容器镜像的 Dockerfile、基础设施的 Terraform)——描述目标状态,而非操作步骤。NixOS 把它推到整台操作系统:整机 = 一份声明

2NixOS 一分钟

NixOS 里,整台机器的状态(装什么包、开什么服务、网络怎么配)全由一份 configuration.nix 声明,nixos-rebuild 把机器变成它描述的样子。三个要点(熟的可跳读):

  • 配置文件:/etc/nixos/configuration.nix(可拆多文件),用 Nix 语言写,声明整台机器该有什么。
  • 应用:nixos-rebuild switch——读配置,构建出对应系统,原子切换过去。
  • 代(generation):每次 rebuild 生成一"代",旧代都保留,启动时能选任意一代(这是回滚的基础,§4)。
configuration.nix · 骨架
{ config, pkgs, ... }:
{
  networking.hostName = "router";
  # ↓ 下面 §3 把路由器的五样东西填进来
}
路由器关联 router-link

2.2 你看到 OpenWRT 的 UCI 把散装命令收进 /etc/config、由 netifd/fw4 落地——那已经是"半声明式"。NixOS 是同一思想的放大版:一份声明描述整机,由 Nix 落地成真实系统,而且构建可复现(同一份配置 → 逐字节相同的系统)。你可以把这份 NixOS 配置理解成"整台机器的 UCI"——只是它管的不止网络,是一切。

3把最小路由器写成声明

3.1 那五步,每一步都能翻译成一个 NixOS 声明选项——命令没了,取而代之的是"该有什么"的描述:

configuration.nix · 路由器
{ config, pkgs, ... }:
{
  networking.hostName = "router";
  networking.useDHCP = false;   # 关掉全局默认,改为逐接口声明

  # ① 配地址:WAN 走 DHCP,LAN 静态网关地址
  networking.interfaces.eth0.useDHCP = true;                   # WAN
  networking.interfaces.eth1.ipv4.addresses = [                # LAN
    { address = "192.168.10.1"; prefixLength = 24; }
  ];

  # ② 打开转发(0.3)
  boot.kernel.sysctl."net.ipv4.ip_forward" = 1;

  # ③ NAT masquerade(0.6 / 1.3)
  networking.nat.enable = true;
  networking.nat.externalInterface = "eth0";    # 出口 = WAN
  networking.nat.internalInterfaces = [ "eth1" ];

  # ④ 防火墙(1.3):默认拦,信任 LAN 侧
  networking.firewall.enable = true;
  networking.firewall.trustedInterfaces = [ "eth1" ];

  # ⑤ dnsmasq:LAN 发 DHCP + DNS(2.3)
  services.dnsmasq.enable = true;
  services.dnsmasq.settings = {
    interface = "eth1";
    dhcp-range = "192.168.10.100,192.168.10.200,12h";
    dhcp-option = [ "3,192.168.10.1" "6,192.168.10.1" ];
  };
}

看这份配置的性质:它不是"一串要执行的动作",而是"这台机器的完整描述"。nixos-rebuild switch 之后,机器就是一台路由器;重启之后依然是——因为这份声明就是系统定义本身,不是临时状态。

声明 ≠ 不懂原理

NixOS 不是魔法:networking.nat.enable 底层还是生成 nftables masquerade(1.3),ip_forward 还是那个内核开关(0.3),dnsmasq 还是 2.3 那个 dnsmasq。NixOS 只是把这些的"配置"从命令变成了声明。正因为你走过 Phase 1/2,这份声明对你是透明的——你知道每行底下发生什么。别人抄一份 NixOS 路由器配置只是"能跑",你是"看得穿地用声明式管理你懂的东西"。

4持久、可复现、可回滚

声明式给路由器三个命令式给不了的性质,而路由器恰恰最需要它们:

持久
声明就是系统定义,重启后仍是它描述的样子。3.1 的"命令重启即失"彻底消失——不用 rc.local、不用 iptables-save。
可复现
同一份 configuration.nix 在另一台机器 rebuild,得到一模一样的路由器。换硬件、做备用机 = 拿文件重建;进 Git 就有完整版本史。
可回滚
每次 rebuild 是一"代",旧代都在。改配置导致断网?重启选上一代,或 --rollback,秒回能用状态。
为什么路由器尤其需要 router-link

路由器是家里的关键基础设施——它挂了,全家断网。命令式路由器一次手滑改错、一次重启丢配置,都可能让你在没网的情况下手忙脚乱。声明式把这些风险摁死:配置持久(不怕重启)、可复现(不怕硬件坏)、可回滚(不怕改错)。你花整门课理解的路由器,用声明式管理,才配得上"可靠基础设施"。

公平对比 OpenWRT

OpenWRT 的 UCI + 备份也能做到大半(配置集中、可备份恢复)。NixOS 的额外优势是:整机可复现(不止网络)、原子升级 + 一键回滚(整个系统层面)、配置即代码(可组合)。代价是 Nix 的学习曲线和生态。两条路都通向"可靠的自建路由器"——选哪个看你更认同哪种哲学,而你现在有能力评判,不是盲选

本章小结

  • 命令式(3.1)告诉机器"做这些动作"(瞬时、易漂移);声明式(NixOS)告诉机器"成为这个样子"(系统保证最终状态)。
  • NixOS:整机 = 一份 configuration.nix 声明,nixos-rebuild switch 落地,每次生成一"代";是 2.2 UCI 思想的放大版(管的不止网络,是一切)。
  • 3.1 五步逐条翻译成声明:interfaces(配地址)、sysctl ip_forward(转发)、networking.nat(NAT)、firewall(防火墙)、services.dnsmasq(DHCP/DNS);底下仍是你学的 nftables / 内核开关 / dnsmasq
  • 声明式让配置透明(你走过 Phase 1/2,知道每行底下是什么)、不漂移、不必手敲,但你依然懂底层。
  • 三大好处:持久(重启不丢)、可复现(换机重建)、可回滚(改坏秒回)——路由器作为关键基础设施尤其需要;OpenWRT UCI 能做大半,NixOS 更彻底。

动手练习

  1. 把 3.1 的五步命令和本章的 NixOS 声明并排列出,逐条对应:每条命令变成了哪个声明选项?哪个"更好维护"?为什么?
  2. 思考题:声明式的"可回滚"对路由器为什么特别重要?举一个命令式路由器会让你"没网又难恢复"、而声明式能秒救的场景。(如:远程改防火墙把自己关在门外——声明式可回滚上一代。)
  3. (熟 Nix 时)在虚拟机 / 双网口测试机上,用本章骨架写一份 configuration.nix,nixos-rebuild switch,验证它成为一台能用的路由器;再故意改错一处、体验回滚。
  4. 进阶:对比 OpenWRT(UCI + 备份)和 NixOS(声明式整机)两种"可靠路由器"方案,各列三个优点、一个代价。结合你自己的使用场景说说会选哪个——你现在有能力做这个判断了。