把家庭网络画成 Reddit 头条:解构 homelab-diagram 的声明式拓扑引擎、线宽物理学与 Agent 制图流

把家庭网络画成 Reddit 头条:解构 homelab-diagram 的声明式拓扑引擎、线宽物理学与 Agent 制图流

在 Reddit 的 r/homelab 社区里,有一类帖子永远占据着全站顶流:那些黑底发光、细节纤毫毕现、信息密度爆炸的家庭实验室全景拓扑图。从最上游的光猫、防火墙、核心交换机,到下游每个物理网口的协商线速,再到 Proxmox 虚拟化集群内的 LXC 容器、Traefik 域名路由,甚至 TrueNAS 存储池中每一块 HDD/SSD 的物理盘位与 VDev 布局——每一张都宛如工业级数据中心的操作手册。然而,绝大多数人为了画出这样一张图,往往需要在 Draw.io、Visio 或 Illustrator 里耗费整整一个周末,手动拖拽成百上千条贝塞尔曲线与矩形,而一旦内网某台虚拟机改了个 IP 或新增了一个 VLAN,整张图的排版维护便瞬间沦为一场灾难。

由开发者 Goblinmine 开源的 homelab-diagram,以极具颠覆性的工程设计打破了这一困境。它既没有选择纯手动作图的繁重,也没有向 Graphviz、Mermaid 等通用算法那混乱不可控的连线妥协,而是开创性地构建了一套 “声明式资产单一真值(YAML)+ 亚像素排版几何引擎(Python)+ AI Agent 闭环审图飞轮(CLAUDE.md)” 的全新制图范式。在这篇文章中,我将结合源码拆解与实战推演,深入剖析它是如何用“线宽物理学”重构网络拓扑表达,并解构其如何通过严苛的几何断言与多模态 Agent 协同,将繁琐的家庭网络资产转化为出版级高保真海报。


一、 拓扑制图的代际演进与能力象限

在深入代码之前,我必须先梳理家庭网络拓扑可视化长期存在的“核心矛盾”。面对复杂的家庭网络基础设施,业界此前主要在两个极端之间摇摆:要么牺牲工程维护性换取美学,要么牺牲网络物理语义换取自动化。

家庭网络拓扑制图的技术演进与能力象限

1.1 手工矢量绘图的“维护黑洞”

在 Draw.io、Figma 或 Visio 中手工绘制拓扑,是 Reddit 社区经典作品最常见的诞生方式。它的上限极高,能够随心所欲定制圆角、间距和阴影。然而,其代价是巨大的:

  • 零数据约束(No Schema):图形之间缺乏结构化数据关联,交换机端口与下游宿主机的连线完全是人为“贴”在一起的;
  • 版本控制与协作断层:静态导出的 XML 或二进制源文件在 Git 中几乎无法 Diff,无法融入持续集成流水线;
  • 重构成本极度高昂:一旦物理交换机增加了两根聚合链路(LACP),上游网段发生了微调,或者存储池新增了两块 NVMe,牵一发而动全身,整张画布的坐标系统几乎需要全量重排。

1.2 通用图生成算法的“语义坍塌”

另一批工程师尝试走向相反的极端:使用 Graphviz(dot)、Mermaid 或 PlantUML。通过文本编写节点和连线关系,依靠力导向算法(Force-Directed Layout)自动计算坐标。然而,通用图引擎并不能理解“计算机网络物理拓扑”的内在规律:

  • 拓扑引力失控:当设备节点超过 20 个、包含多级 VLAN 穿透时,算法会自动收敛成蜘蛛网状,横跨整屏的交叉飞线密密麻麻,视觉灾难无法避免;
  • 缺失物理硬件语汇:通用图元只有简单的椭圆或方形,无法表达交换机的物理端口引脚(RJ45/SFP+)、802.1Q 中继标签(Tagged/Untagged)、机箱文件夹卡片与磁盘阵列晶元。

1.3 homelab-diagram 的第三条路线

homelab-diagram 给出的答案非常清晰:将内容数据与几何排版彻底解耦,由结构化 YAML 统摄资产单一真值,由 Python 脚本驱动确定性亚像素布局,并引入 AI Agent 作为排版微调的“智能手眼”。它既保留了代码化版本管理的严谨,又实现了超越纯人工精细度的出版级美学质感。


二、 全栈架构:从单一真值到 1.6x 矢量渲染

要理解 homelab-diagram 的运行机制,我们可以将其全栈渲染流水线解构为自底向上的五层体系:

homelab-diagram 全栈编译架构与视觉物理渲染流水线

2.1 Layer 1:声明式网络资产单一真值(inventory.yaml)

在整个系统中,inventory.yaml 是全站唯一的真实状态源(Single Source of Truth)。整个网络中的所有物理节点、逻辑划分与业务链路,均在此处被严谨建模:

# 核心 VLAN 规范:定义网络隔离矩阵与主色调
vlans:
  - {key: "10",  tag: "VLAN 10", name: LAN,   subnet: "10.0.10.0/24",    color: "#58a6ff", note: "trusted · may manage the other VLANs"}
  - {key: "20",  tag: "VLAN 20", name: DMZ,   subnet: "10.0.20.0/24",    color: "#f0883e", note: "services · no access to LAN, GUEST or MGMT"}
  - {key: "99",  tag: "VLAN 99", name: MGMT,  subnet: "10.0.99.0/24",    color: "#e3b341", note: "admin interfaces · only from LAN and VPN"}
  - {key: "wan", tag: "Fritzbox", name: WAN,   subnet: "192.168.178.0/24", color: "#f85149", note: "untrusted · uplink"}

# 交换机物理端口级精确映射
switch:
  name: "MikroTik"
  type: "switch"
  model: "MikroTik CRS310-8G+2S+"
  ip: "10.0.99.2"
  ports:
    - {name: ether1, to: "OPNsense", tagged: ["10","20","30","40","99"]}
    - {name: ether3, to: "MADCAT",   untagged: "10"}
    - {name: ether6, to: "U7 Lite",  untagged: "99", tagged: ["10","30","40"]}
    - {name: sfpp2,  to: "terra",    tagged: ["10","20","40","99"]}

通过这一层抽象,网络工程师只需要关注“网络事实”本身:哪个物理端口接了哪台设备、承载了哪些 Tagged/Untagged VLAN、宿主机挂载了什么磁盘,而完全不需要在数据层关心“像素坐标 x, y 是多少”。

2.2 Layer 2:亚像素字体度量与弹性容器计算

在以往的脚本化制图工具中,最容易导致画面崩坏的问题就是文字溢出或标签留白不均。不同字体的字形宽度差异极大,大写字母与小写数字的步长完全不同。

homelab-diagram 在底层直接引入了 Pillow 的真实字形度量引擎:

# build.py 中的高精度字体测量实现
try:
    from PIL import ImageFont
    _FONTS = {700: _font_file("Fira Sans:bold"), 600: _font_file("Fira Sans:semibold")}
    if not all(_FONTS.values()):
        raise RuntimeError("Fira Sans not found")
    _FCACHE = {}

    def measure(s, size, weight=700):
        key = (weight, size)
        if key not in _FCACHE:
            # 采用 8 倍超采样以消除整数舍入带来的栅格阶梯误差
            _FCACHE[key] = ImageFont.truetype(_FONTS[weight], size=size * 8)
        return _FCACHE[key].getlength(s) / 8
except Exception:
    def measure(s, size, weight=700):
        return tw_title(s, size)

通过在渲染前以 8 倍精度动态量化 Fira Sans 与 Fira Code 的实际渲染宽度,系统能够精确计算出每一个设备卡片顶部 Tab 的物理跨度、IP 药丸标签的水平留白,从根本上杜绝了文字撑爆边框或空隙过大的排版瑕疵。


三、 线宽物理学与网络语义编译器

homelab-diagram 之所以能在视觉上带来极强的冲击力,核心在于其创造性地制定了一套“网络图元语义规范”。每一根线条、每一个几何拐角,都严格对应着真实网络工程中的物理属性。

homelab-diagram 官方生成的超高清网络拓扑渲染示例

3.1 速率即线宽(Bandwidth-to-Stroke Mapping)

在传统的拓扑图中,千兆网线和万兆光纤通常被画成完全一样粗细的细线,读者必须费力去阅读微小的文字标注。homelab-diagram 在代码中确立了“线宽物理学”:

# 物理链路协商速率与线宽的严格映射表
SPEED_W = {
    "10G":  9.5,   # 万兆 SFP+ 光纤:粗壮的主干管道
    "2.5G": 3.8,   # 2.5G 电口:中等宽度承载
    "1G":   2.4,   # 千兆接入链路:标准细线
    "100M": 1.4    # 百兆管理口/IoT:纤细微线
}

def trunk_w(speed):
    # 802.1Q 聚合干线外径补偿
    return SPEED_W[speed] + 3.4

当一条万兆光纤从交换机 sfpp2 延伸至核心虚拟化服务器 terra 时,整条线缆呈现出高达 9.5px 的雄浑质感;而连接到硬件 KVM(JetKVM)或 Zigbee 协调器的百兆链路则收敛至 1.4px。无需阅读文字,整座机房的带宽流量主干在第一眼便一览无余。

3.2 802.1Q 双层管道 Trunk 与动态刻度

在处理多 VLAN 汇聚中继(802.1Q Trunk)时,简单的单色线条无法表达“多网段物理共用”的语义。homelab-diagram 采用了一种精妙的双层同心管道渲染技法:

def trunk(self, pts, r=12, sw=7):
    """802.1Q 聚合中继:浅色外套管包裹暗色核心"""
    self.line(pts, TRUNK, sw=sw, r=r)          # 外部宽管道,代表物理承载层
    self.line(pts, BG, sw=sw - 4.4, r=r)       # 内部深色镂空,形成双轨管道管道质感

而在物理交换机网口(Jack)的绘制上,系统更是还原了网工调试时的“跳线打线”视觉:

def ticks(cx, y, untagged=None, tagged=(), up=False):
    """端口 VLAN 刻度:高柱代表 Untagged(本征 VLAN),矮柱代表 Tagged(透传 VLAN)"""
    vk = ([untagged] if untagged else []) + list(tagged)
    x0 = cx - (len(vk) * 7) / 2
    for j, k in enumerate(vk):
        hh = 8 if k == untagged else 5           # Untagged 突出显示为 8px,Tagged 为 5px
        yy = y - hh if up else y
        S.add(f'<rect x="{x0 + j*7:.1f}" y="{yy}" width="5" height="{hh}" rx="1" fill="{C[k]}"/>')

每一个网口下方均排列着对应其放通 VLAN 颜色的小柱子。当一个端口属于 Access 模式时,只有一根高亮的长柱;而作为 Trunk 端口接入 AP 或防火墙时,下方则整齐排列着由多个矮柱组成的彩色色标阵列。

3.3 物理设备与机箱卡片的实体文件夹隐喻

为了在视觉上直观区分“真实物理硬件机箱”与“虚拟出来的虚拟机/容器”,homelab-diagram 摒弃了千篇一律的圆角矩形,定制了带有 物理文件夹标签(Folder Tab)的 SVG 卡片:

def folder(x, y, w, h, tab_w, fill, stroke, sw):
    """利用三阶贝塞尔曲线构建带左上角斜切标签的文件夹机箱轮廓"""
    t = y - TAB
    r, rt, sl = 12, 9, 16
    d = (f"M{x},{y+h-r} L{x},{t+rt} Q{x},{t} {x+rt},{t} L{x+tab_w-sl:.1f},{t} "
         f"C{x+tab_w-sl/3:.1f},{t} {x+tab_w-sl/3:.1f},{y} {x+tab_w+sl/2:.1f},{y} "
         f"L{x+w-r},{y} Q{x+w},{y} {x+w},{y+r} L{x+w},{y+h-r} Q{x+w},{y+h} {x+w-r},{y+h} "
         f"L{x+r},{y+h} Q{x},{y+h} {x},{y+h-r} Z")
    S.add(f'<path d="{d}" fill="{fill}" stroke="{stroke}" stroke-width="{sw}"/>')

光猫(FRITZ!Box)、核心交换机(MikroTik)、防火墙(OPNsense)以及主力宿主机(terra)全部采用了这种带有 name · type 突起 Tab 的实体质感卡片,配合卡片左侧暖色调的 HWC = "#c08a5c" 硬件实体指示条,构筑出极强的机房空间层次感。


四、 重新定义人机协同:基于 CLAUDE.md 的 Agent 制图飞轮

在深入分析该项目时,最令我震撼的并非仅仅是 Python 渲染 SVG 的技巧,而是作者开源的一整套 Agentic 制图工程学。

homelab-diagram 仓库在根目录提供了一份详尽的 CLAUDE.md。它不是普通的开发者指南,而是一份专门指导 AI Agent(如 Claude Code 或各类智能体)如何与人类工程师配合,完成网络勘测、数据建模与视觉排版的“执行操作规程”。

基于 CLAUDE.md 的 AI Agent 辅助制图与质检自愈飞轮

4.1 只读实机遥测提取指令集

许多用户在面对自己庞杂的内网时,往往记不清详细的配置。CLAUDE.md 规范了 Agent 的勘测行为准则:严禁进行任何破坏性操作,仅通过只读命令探查网络底层事实。

Agent 会在用户授权下依次执行以下探查流水线:

  • 网络接口与 IP:ip -br a 快速获取物理网卡与当前链路状态;
  • 虚拟化与容器拓扑:Proxmox 环境下执行 pct list、qm list,探查各 LXC/VM 的 ID、内存分配与运行服务;
  • ZFS 存储池与盘位:zpool list -v 或 TrueNAS API(midclt call pool.query、midclt call disk.query),准确获取磁盘物理型号(如 WD Red Plus 12TB)、RAIDZ 拓扑结构与真实使用百分比;
  • 硬件拓扑:lspci 与 lsblk -o NAME,MODEL,SIZE,识别网卡具体型号(如 Intel i226-V 双口 2.5G)。

通过第一手客观遥测,Agent 能够自动规避人为记忆模糊导致的配置疏漏。

4.2 严苛的几何断言(Assertion Bounding Guards)

在布局阶段,由于不同用户的内网规模千差万别(有的机房有 12 个 DMZ 容器,有的只有 4 个),固定的绝对坐标极易发生溢出碰撞。

作者在 build.py 的关键布局节点注入了严苛的 Python 运行时断言:

# 硬件列表高度越界拦截
assert hy0 + 22 + len(T["hw"]) * (TH_ + TG_) <= hy0 + ZH + 6, "hardware list too tall"

# DMZ 容器多列布局溢出拦截
assert y <= zy + ZH + 6, f"DMZ column {i} too tall"

# 媒体自动化流水线面板与底层宿主机重叠碰撞拦截
assert PY + PH <= ty0 - TAB - 16, f"pipeline panel overlaps terra: {PY + PH} > {ty0 - TAB - 16}"

# 画布总高溢出拦截
assert ty0 + th0 <= H - 20, f"canvas too short: terra ends at {ty0 + th0}"

这一防御性设计赋予了代码极高的鲁棒性:一旦用户新增过多服务导致卡片撑爆,程序绝不会静默渲染出一张文字相互重叠的残次图,而是直接在控制台抛出精准的像素级超界错误,驱动 Agent 自动微调 H、ZH 或重新平衡列间距。

4.3 ImageMagick 局部切图多模态质检闭环

在纯文本终端里,Agent 是“看不见”渲染效果的。为了打破这种盲目性,CLAUDE.md 规定了一套精妙的局部显微镜切图审查流程:

# Agent 在后台渲染后,自动裁剪高密度走线与复杂卡片区域
magick out/homelab.png -crop 1400x600+X+Y +repage -resize 70% /tmp/crop.png

通过将交换机引脚连接处、DMZ 容器列表密集区等“高危碰撞点”裁剪为微缩切片,Agent 能够借助多模态视觉能力自主审视:

  1. 线缆穿插是否有遮罩失效导致的文字糊损?
  2. 聚合链路(Trunk)拐弯圆角是否平滑自然?
  3. 卡片列与列之间的留白是否平衡对称?

整个过程无需人类用户一遍遍肉眼纠错,Agent 在大约 60 轮的后台微调与视觉自愈中持续逼近最优解,最终交付一张令人叹为观止的极客杰作。


五、 实战演练与安全发布指南

如果你也想将自己的家庭网络资产接入这套引擎,以下是我在实操中提炼的核心步骤与避坑要点。

5.1 环境初始化与图标依赖下载

homelab-diagram 自身代码极其克制,仅依赖基础的 Python 生态与矢量工具:

# macOS 环境安装依赖
brew install librsvg

# Python 基础依赖
pip3 install pyyaml pillow

服务图标由独立的 fetch_icons.py 从知名开源图标库 homarr-labs/dashboard-icons 动态拉取。运行该脚本即可一键补全全套服务矢量图:

python3 fetch_icons.py

[!NOTE] 图标 CDN 避坑提示
国内网络在拉取 jsDelivr CDN 时偶发握手超时或 403 阻断。fetch_icons.py 内部原生实现了多源回退(Fallback 到 raw.githubusercontent.com)与三轮重试机制。若遇到特殊内网服务缺少图标,只需在 icons/ 目录下放置同名 SVG 并更新 inventory.yaml 即可。

5.2 安全至上:双态脱敏发布机制

把自己的家庭拓扑发到公开社区(如 Reddit、V2EX 或个人博客)时,最大的安全隐患莫过于泄露自己的真实顶级域名、动态公网 IP 以及内网 Wi-Fi SSID。

homelab-diagram 在设计之初便原生集成了“多态脱敏编译器”:

# build.py 中的脱敏替换逻辑
UNREDACTED = "--unredacted" in sys.argv
RED = INV["redact"]
DOMAIN = INV["meta"]["domain"]
SHOWN_DOMAIN = DOMAIN if (UNREDACTED or not RED["domain"]) else RED["domain_placeholder"]

def sub(s):
    """自动将正文所有动态插值替换为 redacted.com"""
    s = str(s).replace("{domain}", SHOWN_DOMAIN)
    if not UNREDACTED and RED["domain"]:
        s = s.replace(DOMAIN, SHOWN_DOMAIN)
    return s
  • 日常公开输出:直接执行 python3 build.py,系统会自动将所有域名遮蔽为 redacted.com,并将家庭 Wi-Fi 替换为通用的 Home IoT、Guest Wi-Fi;
  • 私人归档版本:执行 python3 build.py --unredacted,系统才会输出带真实资产信息的私密高清图,存入 out/homelab-unredacted.png。

在公开发布前,只需执行一行命令检查,即可彻底杜绝隐私泄露风险:

grep -E "(yourdomain\.com|MyRealSSID)" out/homelab.svg || echo "Safe to publish!"

六、 总结与技术洞见

在体验并剖析了 homelab-diagram 的整体实现后,我深刻感受到了一种现代软件工程的美感。它不仅解决了一个小众极客社区的痛点,更展现了当“声明式数据规范”、“确定性几何排版” 与 “AI Agent 视觉飞轮” 碰撞时所能激发的无限潜能:

  1. 摆脱对复杂商业 GUI 软件的奴役:将视觉资产的控制权重新交还给代码与 Git。当资产发生变更时,修改几行 YAML 并重新触发编译,远比在拖拽画布里手动挪移几十个元素优雅得多;
  2. 给 AI Agent 一个严谨的“物理沙盒”:大语言模型在做发散性创作时往往容易产生幻觉与对齐漂移,但如果为其配备了 Pillow 字形度量、Python 严苛断言与 ImageMagick 视觉反馈切片,Agent 便能化身为最不知疲倦的版面调优大师;
  3. 技术表达的多模态演进:高品质的技术图表绝非浮夸的花架子,它能将上万行繁杂的配置清单压缩为一张一目了然的认知地图。这种兼具科学严谨与视觉美学的工具范式,值得在更多系统工程领域被深入借鉴与推广。

原文链接与参考资料