I run my homelab like a small company: the architecture, the org chart, and the rules I only learned by breaking things
I run my homelab like a small company: the architecture, the org chart, and the rules I only learned by breaking things
我像经营一家小公司一样经营我的家庭实验室:架构、组织架构图,以及那些我通过“搞砸”才学到的规则
Most homelab tours are a hardware list with the price crossed out. This isn’t that. I want to show you how the pieces talk to each other, because after a couple of years the interesting thing about my setup isn’t what’s in it — it’s that it’s organised less like a rack of services and more like a small organisation, with a front desk, a records office, a security guard, a chief of staff, and a set of standing rules that every one of them was written down only after something went wrong. Here’s the org chart, then how a request, a secret, and a scheduled job each actually move through it — and the handful of principles that fell out of two years of getting it wrong.
大多数家庭实验室(homelab)的展示往往只是一个划掉价格的硬件清单。本文并非如此。我想向你展示这些组件是如何相互通信的,因为经过几年的折腾,我的设置中最有趣的地方不在于它包含了什么,而在于它的组织方式——它不像是一堆服务的机架,更像是一个小型组织:有前台、档案室、保安、幕僚长,以及一套在经历过故障后才制定出来的常设规则。以下是组织架构图,以及请求、密钥和定时任务在其中实际流转的方式,还有我在这两年“踩坑”过程中总结出的几条原则。
The org chart
组织架构图
Physically it’s boring: a couple of small servers running a hypervisor, a NAS, a low-power box that does nothing but host local language models, and a router. The roles are where it gets interesting, because each layer has one job and knows almost nothing about the others.
从物理层面看,它很无聊:几台运行虚拟化管理程序的小型服务器、一台 NAS、一台只负责托管本地语言模型的低功耗设备,以及一台路由器。有趣的地方在于角色分配,因为每一层只负责一项工作,且几乎不了解其他层的情况。
The front desk — one door for everything. Every service, without exception, is reached through a single reverse proxy that terminates TLS with real certificates, sitting behind a CDN. In front of the apps is one identity provider doing single sign-on and 2FA. You authenticate once, to one thing, and every service inherits that. (I wrote up the security side of this separately — including the time I ran it with the example config’s placeholder secrets still in place. Securing one door is only simple until you realise it’s also one point of failure.)
前台——万物之门。 每一个服务,无一例外,都通过一个单一的反向代理访问,该代理在 CDN 之后,使用真实证书终止 TLS。在应用前端,有一个身份提供商负责单点登录(SSO)和双重认证(2FA)。你只需对一个入口进行一次身份验证,所有服务都会继承该状态。(我曾单独写过关于安全性的文章,包括我曾因疏忽在配置中保留了示例占位符密钥的经历。保护一个入口很简单,直到你意识到它同时也是一个单点故障。)
The records office — secrets live in one place. No service holds its own credentials on disk. There’s a dedicated secrets store, and everything — the proxy’s DNS token, database passwords, API keys, the identity provider’s signing keys — is pulled from it at runtime via a machine identity, never written into a config file that could end up in a backup or a git history. A secret has exactly one home and one system of record.
档案室——密钥的唯一归宿。 没有任何服务会在磁盘上保存自己的凭据。我有一个专门的密钥存储库,所有内容——代理的 DNS 令牌、数据库密码、API 密钥、身份提供商的签名密钥——都是在运行时通过机器身份从库中提取的,绝不会写入可能进入备份或 git 历史记录的配置文件中。一个密钥只能有一个家,且只能有一个记录系统。
Security — detect in one place, block in another. The identity provider writes a log line for every auth attempt. A separate intrusion-detection service tails those logs, and when it sees a run of failures it pushes the offending address up to the CDN’s edge to be blocked before it reaches the network at all. Detection and enforcement are deliberately different systems that share nothing but a log file and an API. Loose coupling is why it’s never taken anything else down with it.
安全——一处检测,他处封锁。 身份提供商会为每次验证尝试写入一条日志。一个独立的入侵检测服务会跟踪这些日志,当它发现连续失败时,会将违规地址推送到 CDN 边缘,在流量到达网络之前就将其拦截。检测和执行是刻意分开的两个系统,除了日志文件和 API 外,它们不共享任何东西。这种松耦合设计确保了它从未导致其他服务瘫痪。
The chief of staff — one brain, several hats. This is the part people find odd, so it’s worth explaining. The estate is orchestrated by a self-hosted AI agent that runs as a set of profiles, each a different role: one that manages infrastructure, one that writes, one that researches, one that watches security. They share a task board and hand work between them. It sends my morning briefing, drafts documentation, watches for drift, and answers questions about the estate — but, crucially, it is not trusted to gather its own facts (more on that rule below).
幕僚长——一个大脑,多重身份。 这是人们觉得最奇怪的部分,值得解释一下。整个系统由一个自托管的 AI 代理编排,它以一组配置文件的形式运行,每个文件代表不同的角色:一个负责管理基础设施,一个负责写作,一个负责研究,一个负责监控安全。它们共享一个任务板并相互交接工作。它会发送我的晨间简报、起草文档、监控配置漂移并回答关于系统的问题——但关键在于,它不被信任去自行收集事实(关于这条规则下文会详述)。
The workforce — the actual services. Behind all that: the password vault, a git server, a monitoring stack, home automation, a media library with offsite backup, a couple of tools I’ve built, and the local-model host that the AI layer runs against so that most of its work costs nothing and leaves no data with a vendor.
员工——实际的服务。 在这一切背后:密码库、git 服务器、监控栈、家庭自动化、带有异地备份的媒体库、我构建的几个工具,以及 AI 层所依赖的本地模型主机,这样它的大部分工作不仅零成本,也不会将任何数据留在供应商那里。
How a request moves
请求是如何流转的
Say I open my dashboard from a laptop. DNS resolves the hostname. Internally it points at the one machine running the proxy; externally the same name is CDN-fronted. (Split-horizon, and yes, that split has bitten me — a client using the wrong resolver got a page that looked down when the service was fine.) The CDN edge takes the request first. If your address is on the block list the intrusion-detector pushed up there, this is as far as you get. The reverse proxy terminates TLS and checks: is this person already authenticated? If not, it bounces them to the identity provider. The identity provider takes the login and 2FA, and either hands back a session (for the forward-auth apps) or completes an OpenID Connect exchange (for the apps that speak it properly and want to know who you are, not just that you’re allowed). Only now does the request reach the actual service — which has, this whole time, never seen an unauthenticated packet. Five layers, and each one can say no on its own terms. The important property isn’t any single wall; it’s that the app at the end gets to be simple because everything in front of it already did the hard part.
假设我从笔记本电脑打开仪表板。DNS 解析主机名。在内部,它指向运行代理的那台机器;在外部,同一个名称由 CDN 提供支持。(这是“水平分割 DNS”,是的,这种分割曾让我吃过苦头——客户端使用了错误的解析器,导致在服务正常的情况下页面却显示无法访问。)CDN 边缘节点首先接收请求。如果你的地址在入侵检测器推送的黑名单中,那么请求到此为止。反向代理终止 TLS 并检查:此人是否已通过身份验证?如果没有,它会将用户重定向到身份提供商。身份提供商处理登录和 2FA,要么返回一个会话(针对前向认证应用),要么完成 OpenID Connect 交换(针对那些能正确识别协议并需要知道你是谁,而不仅仅是确认你是否有权访问的应用)。直到这时,请求才到达实际的服务——在此期间,该服务从未见过任何未经身份验证的数据包。五层防护,每一层都可以根据自己的规则拒绝请求。重要的属性不在于任何单一的墙,而在于末端的应用可以保持简单,因为在它之前的所有层都已经完成了最困难的工作。
How a secret moves
密钥是如何流转的
This one’s shorter and it’s the habit I’d most want a new self-hoster to steal. Nothing has its password baked in. A service boots, authenticates to the secrets store with a machine identity that was itself provisioned once and lives in an environment file with tight permissions, pulls exactly the secrets it needs into memory, and runs. Rotate a credential in one place and every consumer picks up the new one on restart. When I audited the estate, the single best thing I could say about it was that I could rotate any password without hunting through a dozen docker-compose.yml files — because none of them held one. The corollary, learned the hard way: verify a secret by fingerprint, never by printing it. The moment you echo a key to check it matches, it’s in a scrollback buffer and a terminal log. I compare SHA-256 hashes across the three places a secret lives and never look at the value itself.
这一点比较简短,也是我最希望新入坑的自托管用户学习的习惯。没有任何服务的密码是硬编码的。服务启动时,使用一个预先配置好并存放在权限严格的环境文件中的机器身份,向密钥存储库进行身份验证,将所需的密钥提取到内存中并运行。在一个地方轮换凭据,所有使用者在重启后都会获取新凭据。当我审计整个系统时,我能说的最好的一点是:我可以轮换任何密码,而无需翻遍十几个 docker-compose.yml 文件——因为它们里面一个密码都没有。由此得出的推论(也是我通过惨痛教训学到的):通过指纹验证密钥,永远不要打印它。当你为了检查匹配而 echo 一个密钥时,它就已经进入了滚动缓冲区和终端日志中。我通过比较密钥存在的三处位置的 SHA-256 哈希值来验证,从不查看密钥本身的值。
How a scheduled job moves — and the one rule that governs all of them
定时任务是如何流转的——以及统领所有任务的那条规则
This is the principle the whole estate is built on, and it came from a specific failure. I used to have the AI layer run health checks and “find me opportunities” scans — let the model go and look at the system and report back. It hallucinated. It reported backups that hadn’t run as successful. It cited demand for a product with no source. It appended a “[SILENT]” tag to a report while violating its own instruction not to. A model asked to gather facts will, under the slightest ambiguity, invent them and present…
这是整个系统构建所基于的原则,它源于一次具体的故障。我曾经让 AI 层运行健康检查和“寻找机会”扫描——让模型去查看系统并汇报。结果它产生了幻觉。它报告说未运行的备份已成功完成。它引用了一个不存在的产品需求。它在报告中添加了“[SILENT]”标签,同时违反了它自己“不要这样做”的指令。当一个模型被要求收集事实时,只要有哪怕一点点模糊性,它就会编造事实并呈现……