3.1 结尾留了个问题:那三十行命令重启就没了。这章给答案——用 NixOS 把整台路由器写成一份声明。声明式的意思是:你不再"敲命令让机器变成路由器",而是"写下这台机器应该是什么样"(有哪些接口、哪条 NAT、哪个 dnsmasq),系统负责让实际状态永远等于你的声明。3.1 那五步的"意图",会变成一份能进 Git、能回滚、能在任何机器上复现的配置文件。你熟 NixOS,这章正对路子——把学过的路由器知识,用声明式的方式固化下来。
声明式路由器 = 把 3.1 那五步(接口、转发、NAT、防火墙、dnsmasq)写成一份 NixOS 配置声明;nixos-rebuild 后,系统保证实际状态等于声明——重启不丢、换机可复现、改坏能回滚。
1命令式 vs 声明式
3.1 是命令式(告诉机器"做这些动作");NixOS 是声明式(告诉机器"成为这个样子")——区别在于谁来保证最终状态。
sysctl -w …
nft add rule …
dnsmasq …
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)。
{ config, pkgs, ... }:
{
networking.hostName = "router";
# ↓ 下面 §3 把路由器的五样东西填进来
}
2.2 你看到 OpenWRT 的 UCI 把散装命令收进 /etc/config、由 netifd/fw4 落地——那已经是"半声明式"。NixOS 是同一思想的放大版:一份声明描述整机,由 Nix 落地成真实系统,而且构建可复现(同一份配置 → 逐字节相同的系统)。你可以把这份 NixOS 配置理解成"整台机器的 UCI"——只是它管的不止网络,是一切。
3把最小路由器写成声明
3.1 那五步,每一步都能翻译成一个 NixOS 声明选项——命令没了,取而代之的是"该有什么"的描述:
{ 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持久、可复现、可回滚
声明式给路由器三个命令式给不了的性质,而路由器恰恰最需要它们:
--rollback,秒回能用状态。路由器是家里的关键基础设施——它挂了,全家断网。命令式路由器一次手滑改错、一次重启丢配置,都可能让你在没网的情况下手忙脚乱。声明式把这些风险摁死:配置持久(不怕重启)、可复现(不怕硬件坏)、可回滚(不怕改错)。你花整门课理解的路由器,用声明式管理,才配得上"可靠基础设施"。
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 更彻底。
动手练习
- 把 3.1 的五步命令和本章的 NixOS 声明并排列出,逐条对应:每条命令变成了哪个声明选项?哪个"更好维护"?为什么?
- 思考题:声明式的"可回滚"对路由器为什么特别重要?举一个命令式路由器会让你"没网又难恢复"、而声明式能秒救的场景。(如:远程改防火墙把自己关在门外——声明式可回滚上一代。)
- (熟 Nix 时)在虚拟机 / 双网口测试机上,用本章骨架写一份 configuration.nix,
nixos-rebuild switch,验证它成为一台能用的路由器;再故意改错一处、体验回滚。 - 进阶:对比 OpenWRT(UCI + 备份)和 NixOS(声明式整机)两种"可靠路由器"方案,各列三个优点、一个代价。结合你自己的使用场景说说会选哪个——你现在有能力做这个判断了。