<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Digital Homestead]]></title><description><![CDATA[Engineering a resilient, local-first smart home.

The Digital Homestead explores the intersection of home automation and professional infrastructure. We move beyond consumer gadgets to build hardened environments using zone-based networking, automated deployment pipelines, and local-only protocols. No cloud dependencies, no walled gardens; just infrastructure that works.]]></description><link>https://blog.wizard-networks.com</link><image><url>https://cdn.hashnode.com/uploads/logos/69c2191530a9b81e3aee5921/c6afb3ea-9d25-48fc-964b-2759852599fa.jpg</url><title>The Digital Homestead</title><link>https://blog.wizard-networks.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 18 Aug 2026 05:24:13 GMT</lastBuildDate><atom:link href="https://blog.wizard-networks.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[That "Digital Download" You Just Bought on Etsy Might Be Free]]></title><description><![CDATA[A Quick Detour This one's a bit different from what I usually post here on The Digital Homestead. I got sidetracked digging into something while working on a pricing project, and it turned into a rabb]]></description><link>https://blog.wizard-networks.com/that-digital-download-you-just-bought-on-etsy-might-be-free</link><guid isPermaLink="true">https://blog.wizard-networks.com/that-digital-download-you-just-bought-on-etsy-might-be-free</guid><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Sat, 25 Jul 2026 19:01:07 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>A Quick Detour</strong> This one's a bit different from what I usually post here on The Digital Homestead. I got sidetracked digging into something while working on a pricing project, and it turned into a rabbit hole worth writing up. So consider this a short break from the regular content, we'll be back to the usual soon.</p>
</blockquote>
<p>I was pricing out a 3D-printed design for my brother recently, trying to figure out what he should charge, when I went looking for comps on Etsy. That's when I found it: a shop with over 80 sales of a "digital download," a single STL file for 3D printing, going for a few dollars a pop.</p>
<p>Nothing wrong with that on its own. Except the file wasn't original. It had been downloaded from a free file-sharing site for 3D printing designs, where the actual creator had posted it under a Creative Commons license, and re-listed on Etsy with a small attribution credit tucked into the first product photo.</p>
<p>Curious, I looked at the rest of the shop. It has around 100 listings, and it looks like every single one is built the same way: a free CC BY file, downloaded and resold with attribution.</p>
<p>Legally, that's completely fine. But it made me realize how many people don't actually know what they're buying, or what license terms even mean. So here's a plain-language breakdown.</p>
<hr />
<h2>What "Creative Commons" Actually Means</h2>
<p>Creative Commons (CC) licenses are a set of standardized permissions creators can attach to their work (designs, photos, music, writing, whatever) that spell out exactly what other people are allowed to do with it. Instead of full "all rights reserved" copyright, the creator picks a license that grants specific freedoms up front.</p>
<p>There are a handful of common variants, and the differences matter a lot:</p>
<ul>
<li><p><strong>CC0</strong>: No rights reserved. Do anything you want, no credit required.</p>
</li>
<li><p><strong>CC BY (Attribution)</strong>: Do anything you want, including sell it, as long as you credit the original creator.</p>
</li>
<li><p><strong>CC BY-SA (Attribution-ShareAlike)</strong>: Same as above, but anything you make from it has to be shared under the same license terms.</p>
</li>
<li><p><strong>CC BY-NC (Attribution-NonCommercial)</strong>: You can use and share it, credit required, but you can't sell it or use it commercially.</p>
</li>
<li><p><strong>CC BY-ND (Attribution-NoDerivatives)</strong>: You can share it as-is with credit, but you can't modify or remix it.</p>
</li>
<li><p><strong>CC BY-NC-SA / CC BY-NC-ND</strong>: Combinations of the above, all with the non-commercial restriction.</p>
</li>
</ul>
<p>The file in question was licensed <strong>CC BY</strong>, Attribution only. That means anyone can download it, print it, sell the prints, or even resell the file itself, for free or for profit, with zero permission needed from the original designer. The only requirement is giving them credit somewhere.</p>
<hr />
<h2>So the Reseller Didn't Do Anything Wrong</h2>
<p>That's the surprising part. It feels like it should be a gray area, but it isn't. The original designer chose CC BY specifically because it allows commercial use. They knowingly gave up the right to control who profits from their work, in exchange for wider distribution and, honestly, some free advertising every time someone credits them.</p>
<p>The ethics are a little murkier than the legality. The designer probably didn't picture someone taking their free file and turning it into 80+ paid sales without doing any of the actual design work. But that's the deal CC BY licenses make, and plenty of designers use it anyway because attribution and exposure are worth more to them than a paywall.</p>
<hr />
<h2>The Part That Should Give Buyers Pause</h2>
<p>Here's the actual problem: most buyers have no idea any of this is happening. They see a nicely photographed product listing, a few reviews, a price tag, and they buy it assuming they're paying for something exclusive or hard to find. In reality, they could often find the exact same file, from the exact same creator, for free, with a two-minute search.</p>
<p>If you're ever buying a "digital download" STL, PDF pattern, clipart bundle, or similar file online, it's worth a quick gut-check before you pay:</p>
<ol>
<li><p><strong>Search the product name.</strong> Try the product name (or a close description of it) plus the word "free," or the name of a known file-sharing platform for that type of file. For 3D printing specifically, that means checking sites where designers post files for free before assuming a paid listing is your only option.</p>
</li>
<li><p><strong>Check for attribution text.</strong> If the listing includes small, easy-to-miss attribution text, often buried in a corner of the first photo, that's a signal the file may exist elsewhere for free.</p>
</li>
<li><p><strong>Search the designer's name.</strong> That attribution is also your best lead. Search the designer's name directly, and it will often take you straight to the platform where they originally posted the file, usually for free.</p>
</li>
</ol>
<p>None of this means every paid digital download is a scam. Plenty of creators sell their own original work, and plenty of resellers add real value: better instructions, extra formats, curation, or a way to help support the free-file ecosystem, even if the underlying design is technically also legal to redistribute free. But it's worth knowing what you're actually paying for.</p>
<hr />
<h2>And If You're the One Downloading</h2>
<p>It works the other way too. If you're browsing one of these free file-sharing sites looking for a design to print and sell yourself, don't assume "free to download" means "free to sell."</p>
<p>Check the license on every file before you list it anywhere. If it's marked <strong>CC BY-NC</strong>, that Non-Commercial part means you can download it, print it, and use it for personal projects, but you cannot sell it or profit from it in any way. Only plain <strong>CC BY</strong> or <strong>CC0</strong> actually clear you to sell commercially. Mixing that up is an easy mistake to make, and it's the kind of thing that can get a listing pulled or land you a takedown notice.</p>
<hr />
<h2>The Takeaway</h2>
<p>Creative Commons licensing is a genuinely good system. It lets creators share work broadly while still getting credit, and it lets other people build businesses around distribution, printing, or customization without needing a lawyer involved. But it only works well when both sides understand the terms. Most people, it turns out, don't read the license. They just see a price tag and assume it means something it doesn't.</p>
<blockquote>
<p><strong>If you're a maker, know your license before you post something. If you're a buyer, it costs nothing to check.</strong></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Off-Label Git: A Secure Data Store for an AI Agent Running Real Storefronts
]]></title><description><![CDATA[In my last post about The Construct, I mentioned building a Claude Code skill to onboard new services into the deployment pipeline. That worked well enough that I kept pulling on the thread: if an AI ]]></description><link>https://blog.wizard-networks.com/off-label-git</link><guid isPermaLink="true">https://blog.wizard-networks.com/off-label-git</guid><category><![CDATA[home lab]]></category><category><![CDATA[homelabbing]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[AI Agents: Transforming Business Operations]]></category><category><![CDATA[AI agents 2026]]></category><category><![CDATA[AI agents for business automation]]></category><category><![CDATA[Git]]></category><category><![CDATA[gitea]]></category><category><![CDATA[Model Context Protocol]]></category><category><![CDATA[model context protocol security]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[Security]]></category><category><![CDATA[automation]]></category><category><![CDATA[GIT LFS]]></category><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Mon, 06 Jul 2026 21:14:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/257de377-5bed-4e17-9666-567efd3e4eda.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>In my last post about The Construct, I mentioned building a Claude Code skill to onboard new services into the deployment pipeline. That worked well enough that I kept pulling on the thread: if an AI agent can be trusted to write deployment configs, what else can it be trusted to run day-to-day? The answer I landed on was a small storefront operation I run out of the same homelab (mostly 3D-printed and laser-cut products), and that raised a question the deployment skill never had to answer, because Ansible playbooks don't need write access to real, ongoing storefront data.</p>
<p>The question was: how much access does an agent actually need, and how do you make sure it never gets more than that, while still keeping the data it does need right at its fingertips, not buried behind a request I have to broker by hand every time?</p>
<hr />
<h2>The Setup: Several Storefronts, One Assistant</h2>
<p>I run a handful of small storefronts side by side: different product lines (3D-printed goods, laser-cut goods, a couple of others), different workflows, different data. Each one now has a dedicated Cowork skill: read a cost ledger, draft a listing, close out an issue, tell me what's pending. That's genuinely useful, right up until you think about what it implies. An assistant that can write to live, ongoing storefront data needs somewhere to write to, and "somewhere" is exactly the kind of vague scope that turns into a real problem the first time a skill misfires.</p>
<p>I'd already gone through this exercise once, with the Zone-Based Firewall from a couple posts back: don't give broad trust when a narrow boundary does the same job. Same principle, one layer up the stack. This time the thing needing a boundary wasn't a Zigbee bulb, it was an agent with write access to a git repo.</p>
<hr />
<h2>Repurposing a Repo: Not for Code, for Context</h2>
<p>Worth naming the obvious oddity here: a git repo is built for source code, not cost ledgers and listing copy. That's a non-traditional use of the tool, and I went with it anyway, because the properties that make a repo good for code turn out to matter just as much for storefront data: versioned history, plain-text files that diff cleanly, a folder structure that means something on its own.</p>
<blockquote>
<p>The part that sold me wasn't the versioning. It's that the same file works twice. A markdown file sitting in a repo is presentable to me (I can open the folder and read it like a document), and it's just as legible to the agent reading it back for context. One data store, two audiences, no translation layer in between.</p>
</blockquote>
<p>That second audience is the real shift. One of my skills even standardizes its draft files on a plain <code>README.md</code> instead of a custom filename, purely because it renders natively the moment the folder is opened, presentable to a human glancing at the repo, and parsed the same way by the agent working inside it. A repo stopped being just where code lives and became the shared surface where I hand data to my agent team and get it back in a form I can actually read.</p>
<hr />
<h2>Why Cowork, Not Code</h2>
<p>I could have wired this up as a Claude Code integration instead. It's closer to how The Construct's onboarding skill runs. I deliberately didn't. Cowork's whole shape is built around a single local MCP connection per session; there's no path for it to reach past that connection unless I go build one myself. Claude Code assumes a broader kind of trust by design: it expects to operate across a whole codebase with shell access, and I'd have had to manually claw all of that back down to get the same guarantee Cowork gives me for free.</p>
<p>Choosing Cowork wasn't about which tool was more convenient. It was picking the tool whose default shape already matched the boundary I wanted, instead of building a boundary on top of a tool that assumes it doesn't need one.</p>
<hr />
<h2>The Hard Boundary: One Org, Nothing Else</h2>
<p>The lazy version of this is pointing an agent at "my Gitea" and calling it done. I didn't do that. There's a local MCP server dedicated to this entire arrangement, and it authenticates as a dedicated Git account whose own permissions are limited to exactly one org and that org's repos: not my account, not my other projects, one org.</p>
<blockquote>
<p>The distinction matters. Cowork can read and write inside that org's repos all day. It has no path to anything outside it: no other orgs, no other repos, no way for a misfiring skill to reach past the fence, because there's nothing on the other side of the fence for it to reach, and no account credential that would let it try.</p>
</blockquote>
<p>This is the same shape as the Guest Zone from the firewall post: access to exactly what's inside the boundary, enforced twice over, by the MCP server's own scope and by the account it authenticates as, not by good behavior.</p>
<hr />
<h2>The Soft Boundary: One Repo Per Storefront</h2>
<p>Inside the org, the repos split by storefront, and each skill is written to operate on its own repo only. A couple of the storefronts do legitimately overlap (shared source designs, overlapping audiences between the 3D-printed and laser-cut lines), and the skills know about that overlap and flag it when relevant. They just don't act across the boundary to resolve it themselves.</p>
<p>Worth being precise about what kind of boundary that is. The org scope is enforced twice: once by the MCP server's own configuration, and again by the Git account the MCP server authenticates as, whose permissions are limited to that org and its repos regardless of what the MCP config says. Neither layer has a setting that lets a skill see outside it. The repo-per-storefront split is a different kind of thing entirely: it's enforced by how the skills are written, a convention, not a permission either layer checks. It's the difference between a locked door and an agreement not to open one. Both matter. Only one of them is unbreakable.</p>
<hr />
<h2>Solving the Binary Problem: Git LFS</h2>
<p>Scoping access solved half the problem. The other half was that a git repo without LFS falls over the moment you commit real production files, and 3D and laser production files are not small: print files for one, cut files for the other.</p>
<p>LFS is enabled at the org level, with storage redirected server-side to a separate, larger pool built for it. Each repo's <code>.gitattributes</code> tracks the binary types it actually has reason to hold:</p>
<pre><code class="language-plaintext">*.stl filter=lfs diff=lfs merge=lfs -text
*.3mf filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
</code></pre>
<p>Not every repo needs the same rules: the 3D-printed line tracks <code>.stl</code>/<code>.3mf</code>, the laser line tracks its own cut-file formats, and one of my repos is text and copy only with no binary asset to track at all. That's not an oversight. Enable LFS where the problem exists, skip it where it doesn't.</p>
<p>What this actually bought me is the thing I was missing before: a product's real print or cut files now live in the same folder as the listing copy describing them, under the same commit history, instead of two systems that only stayed in sync because I remembered to update both by hand. That's a genuine long-term storage solution for production files, with the diffable, text-based organization a git repo gives you for free.</p>
<hr />
<h2>Where the Agent Still Isn't Trusted</h2>
<p>None of this means Cowork writes the binaries itself. The MCP's file-write call takes a plain string. Push raw binary bytes through a string parameter and you risk silent corruption. And a corrupted production file is a worse outcome than no file at all.</p>
<p>So the skills are explicit about the limit: draft the text, reference real filenames once I've given them, and leave the actual binary commit to me, by hand, the way I'd do it anyway. It's a deliberate seam, not a gap nobody's gotten to yet.</p>
<blockquote>
<p>Giving an agent full context on a storefront's assets (real filenames, real folder structure, what's committed versus what's still a placeholder) doesn't require giving it a write path into a format it can silently break. It gets to know everything. It doesn't get to touch the one part of the pipeline where a mistake is expensive.</p>
</blockquote>
<hr />
<h2>The Whole Picture</h2>
<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/803aa0a7-2c4e-4c11-ab68-045da3927fb1.png" alt="" style="display:block;margin-left:auto" />

<hr />
<p>Everything in Phase 1 is where the agent actually lives: one connection, one gate, no path outward if the answer is no. Phase 2 is the explicit read/write line running to every storefront repo, none skipped, none reached by any other route. Phase 3 is the split that matters most: text goes straight from the agent into the repo, binaries get routed to LFS and committed by hand, and one storefront never even enters that second path because it has no binaries to begin with.</p>
<hr />
<h2>The Result</h2>
<p>Several storefronts, one scoped org, LFS handling the 3D and laser production files a plain git repo was never going to manage gracefully, and an assistant that can act across all of it without holding a key to anything else. The hard boundary is the org scope: that one's enforced whether the skills behave or not. The soft boundaries (repo-per-storefront, text-only writes) are discipline, documented in the skills themselves, doing the same job a stricter permission model would, without needing one.</p>
<p>Same lesson as the firewall, just applied to an agent instead of a network: figure out the smallest boundary that still gets the job done, then build it so nothing has to remember to respect it.</p>
]]></content:encoded></item><item><title><![CDATA[From Scripts to Single Source: The Construct for Reproducible Home Infrastructure]]></title><description><![CDATA[Most home labs are a patchwork of one-off scripts. A Bash file here, a manual docker compose up there, and a README note that says "run this first." It works until it doesn't. When something breaks at]]></description><link>https://blog.wizard-networks.com/homelab-centralized-deployment</link><guid isPermaLink="true">https://blog.wizard-networks.com/homelab-centralized-deployment</guid><category><![CDATA[centralized homelab deployment pipeline]]></category><category><![CDATA[infrastructure as code for home labs]]></category><category><![CDATA[ansible roles docker compose deployment]]></category><category><![CDATA[secure secret handling docker .env ram]]></category><category><![CDATA[prevent docker nfs root filesystem overwrite]]></category><category><![CDATA[homelab iac]]></category><category><![CDATA[self-hosted pipeline native secrets]]></category><category><![CDATA[proxmox lxc ansible automation]]></category><category><![CDATA[molecule testing ansible home lab]]></category><category><![CDATA[mqtt telemetry homelab notification]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[IaC (Infrastructure as Code)]]></category><category><![CDATA[IaC Automation]]></category><category><![CDATA[Iac Security]]></category><category><![CDATA[iac secrets]]></category><category><![CDATA[IaC Demystified]]></category><category><![CDATA[ansible]]></category><category><![CDATA[ansible-playbook]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[Infrastructure as Code (IaC)]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Devops articles]]></category><category><![CDATA[Docker]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[Security]]></category><category><![CDATA[security secret]]></category><category><![CDATA[mqtt]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[structuring infrastructure as code for AI agents]]></category><category><![CDATA[AI-Ready Infrastructure]]></category><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Sat, 23 May 2026 00:04:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/7a145987-7143-448d-8d4b-bb5c52b81c7e.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most home labs are a patchwork of one-off scripts. A Bash file here, a manual <code>docker compose up</code> there, and a README note that says "run this first." It works until it doesn't. When something breaks at 11 p.m., you're left reconstructing what you did six months ago to get a service running.</p>
<p>There's a better way. Treat every service in your home lab the same: same deployment process, same security model, same observability. Not because it's merely elegant, but because consistency is what makes a home lab maintainable.</p>
<p>This post walks through how I built that system, and more importantly, why you should too.</p>
<h3>What Is Infrastructure as Code?</h3>
<hr />
<p>Before getting into the specifics, it's worth establishing what infrastructure as code actually means in practice, because the term gets thrown around loosely.</p>
<p>The idea is simple: every configuration decision that describes your infrastructure is captured in a file, committed to version control, and applied by a machine rather than typed by hand. Instead of SSH-ing into a server and running commands, you write a playbook that describes the desired state, and a tool like Ansible makes it so.</p>
<p>This isn't just about automation. The deeper value is that <strong>your infrastructure becomes auditable, reproducible, and moveable</strong>. Anyone looking at your repository can understand exactly what a server does and why. Rebuilding a failed node is re-running a playbook, not piecing together notes from memory. Migrating a service to new hardware is changing one variable.</p>
<p>Done right, you should never have a server you're afraid to wipe.</p>
<h3>Stop Writing Deployment Scripts Per Service</h3>
<hr />
<p>Here's the trap most people fall into: they write deployment logic inside each service's repository. Service A has its own <code>deploy.sh</code>. Service B has a different one. Service C has a GitHub Action. After a while, you have six services and six different deployment models. You fix a security bug in one and forget to apply it to the others.</p>
<p>The solution is a <strong>single, centralized pipeline repository</strong> that contains all the deployment logic for your entire cluster. Every service points at it. Every service benefits when it improves.</p>
<p>I call mine <strong>The Construct</strong>. It's a single Ansible roles library. When I bring a new service into the cluster, I don't write new deployment logic. I write a short configuration file that hands off to the same roles every other service uses.</p>
<p>When I fixed the secret handling to use RAM instead of disk, every service in the cluster got that fix on its next deploy. When I added automatic health checks, every service got health checks. One improvement, total coverage.</p>
<p>That's the leverage you get from centralizing.</p>
<h3>The Architecture: Core &amp; Satellite</h3>
<hr />
<p>The Construct follows a <strong>Core &amp; Satellite</strong> model. The central Construct repository holds all the shared Ansible roles. Individual Code repos each contain a single <code>deploy.yml</code> that imports those roles and supplies service-specific configuration: the hostname, the project path, the secrets, the health check endpoints.</p>
<pre><code class="language-plaintext">The Construct (shared roles, one place, one version)
└── roles/
├── docker-deploy      ← handles every Docker service
├── lxc-provision      ← provisions the host itself
├── nfs-mount          ← mounts storage
├── service-validation ← health checks
├── mqtt-notify        ← telemetry
├── recordDeploymentMetadata ← audit trail
└── image-prune        ← cleanup

The Code (e.g., Caliper)
└── deploy.yml               ← ~50 lines of config, no logic
</code></pre>
<p><strong>The Architect</strong> is the orchestrator, self-hosted and local with no cloud CI. When a deploy triggers, The Architect clones both The Construct and the Code repo into a workspace and hands control to Ansible. It also supplies the vault secrets (API keys, database passwords) that services need at runtime.</p>
<p>The execution targets live in <strong>The Galaxy</strong>: Alpine or Ubuntu LXC containers running on PVE nodes, each dedicated to a single service and named after a Star Wars planet. Ansible reaches them over SSH through the bastion host via ProxyJump. Nothing in the cluster is directly reachable from the management plane.</p>
<h3>Built to Move</h3>
<hr />
<p>The other discipline worth enforcing from day one: <strong>nothing in your infrastructure should be hard to rebuild or migrate</strong>.</p>
<p>This is where IaC pays off beyond just automation:</p>
<ul>
<li><p><strong>Hosts are code.</strong> Every node in The Galaxy is provisioned by the <code>lxc-provision</code> role. Its VMID, hostname, IP, OS template, and NFS configuration live in a playbook. Reprovisioning a failed node is one Ansible run.</p>
</li>
<li><p><strong>The server holds a container. The repo holds the truth.</strong> Service configuration lives in Git, not in the server's filesystem. If the server disappears, the configuration doesn't.</p>
</li>
<li><p><strong>Secrets are portable.</strong> They live in The Architect's variable groups, not on disk, not in the repo. Moving to a new Architect instance means exporting the vault. Nothing is trapped.</p>
</li>
<li><p><strong>Every deploy is idempotent.</strong> Running the same playbook twice with no changes produces no changes. Re-deploying is always safe, which means rollbacks and re-runs are never scary.</p>
</li>
</ul>
<p>If a PVE node failed today, I could rebuild every service it hosted by running playbooks against a new node. No tribal knowledge required. No reconstructing configuration from memory. The code is the documentation.</p>
<p>Aim for that. If you have a server you'd be afraid to wipe and rebuild, that's a problem to fix.</p>
<h3>The Eight-Phase Deployment</h3>
<hr />
<p>Every service in the cluster runs through the same eight-phase sequence. Having a standard flow means there are no surprises when something goes wrong. You know exactly where in the pipeline a failure occurred.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/1ccb69ac-da76-4b23-bf13-275b0d5122fa.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li><p><strong>Notify:</strong> MQTT status published as "Updating". Home Assistant sees this immediately.</p>
</li>
<li><p><strong>NFS Validation:</strong> If the service uses network storage, the Black-Hole Check runs before anything else proceeds.</p>
</li>
<li><p><strong>Alpine Dependencies:</strong> For Alpine nodes, <code>rsync</code> and <code>nfs-utils</code> are installed dynamically if needed.</p>
</li>
<li><p><strong>Sync:</strong> The Code repo is rsynced to <code>/opt/&lt;service&gt;/</code> on the node, excluding anything in <code>.deployignore</code>.</p>
</li>
<li><p><strong>Secret Injection &amp; Docker Up:</strong> The atomic block: write to RAM, launch the stack, wipe from RAM.</p>
</li>
<li><p><strong>Health Checks:</strong> HTTP responses, DNS resolution, and database readiness (Postgres, MySQL, Redis) are probed against configured endpoints.</p>
</li>
<li><p><strong>Audit Log:</strong> The Git SHA, deployer identity, branch, and a UTC timestamp are written to <code>/etc/wizard/deployments/</code> on the node.</p>
</li>
<li><p><strong>Image Prune &amp; Final Notify:</strong> Dangling Docker layers are removed, and a success event publishes to MQTT.</p>
</li>
</ol>
<p>Every phase runs inside <code>block/rescue</code>. If anything breaks, MQTT publishes a failure event before the error propagates. The broker always reflects current state.</p>
<h3>Deep Dive: Phase 5 &amp; The Secret Handling Problem</h3>
<p>Here's a specific problem worth solving deliberately: where do your secrets go during a deploy?</p>
<p>The obvious answer is a <code>.env</code> file. Copy it to the server, <code>docker compose</code> picks it up. It works. It's also a quiet security failure. That file sits on disk indefinitely, shows up in backups, and travels wherever your rsync does. Most home labs have a graveyard of <code>.env</code> files in <code>/opt/</code> that nobody remembers putting there.</p>
<p>The better answer: <strong>secrets should never touch disk at all.</strong></p>
<p>The Construct implements a RAM-only secrets model. When a service needs secrets injected at deploy time, the <code>docker-deploy</code> role writes the <code>.env</code> content to <code>/dev/shm</code> (a <code>tmpfs</code> mount that exists only in RAM), launches the stack, then immediately deletes the file in an <code>always</code> block that runs whether the deploy succeeded or failed.</p>
<pre><code class="language-yaml">always:
  - name: SECURE CLEANUP - Remove .env from RAM
    ansible.builtin.file:
      path: "/dev/shm/{{ mqtt_topic }}.env"
      state: absent
    when: envContent | default('') != ''
</code></pre>
<p>The file never touches the physical disk of the Galaxy node. If the deploy crashes mid-run, the next run sweeps orphaned files before doing anything else. Ansible logs never capture the secret content because <code>no_log: true</code> suppresses the task output entirely.</p>
<blockquote>
<p>If you're wondering how containers stay authenticated once that file vanishes, it's because <code>docker compose up -d</code> completely parses the <code>.env</code> file at instantiation. It injects those variables directly into the isolated process memory space of the newly spawned containers. Once the &gt;stack is initialized, the container holds onto those keys internally, allowing the pipeline to safely shred the physical file from RAM without interrupting the active runtime environment.</p>
</blockquote>
<p>This is the kind of thing that's easy to implement once and hard to remember to implement in every one-off script. A centralized pipeline means you make the right call once and it applies everywhere.</p>
<h2>The Black-Hole NFS Check</h2>
<p>If your services use network storage, add this check to your pipeline. When an NFS mount fails silently, Docker writes to the local root filesystem instead, and you won't notice until something fills up.</p>
<blockquote>
<p>The trap with typical directory checks (like Bash’s <code>-d /mnt/nfs</code>) is that if a remote share drops or fails to mount, the local mount-point folder itself still technically exists on the host's root disk—it's just completely empty. A standard script sees the folder, assumes everything is fine, and lets Docker blindly dump gigabytes of data directly onto your host's root OS drive.</p>
</blockquote>
<p>By using Ansible's <code>stat</code> module to compare the underlying storage device IDs (<code>stat.dev</code>), we can programmatically verify whether the mount point has successfully decoupled from the system root:</p>
<pre><code class="language-yaml">- name: Stat NFS mount point
  ansible.builtin.stat:
    path: "{{ nfsMountPath }}"
  register: mount_stat

- name: Stat system root
  ansible.builtin.stat:
    path: "/"
  register: root_stat

- name: Fail if NFS not mounted separately
  ansible.builtin.fail:
    msg: "NFS mount check failed: {{ nfsMountPath }} is on the root filesystem."
  when: mount_stat.stat.dev == root_stat.stat.dev
</code></pre>
<p>If <code>dev</code> matches root, the NFS share isn't mounted. The deployment aborts before anything runs. It's a one-time addition to your pipeline that protects every service that uses shared storage.</p>
<h2>MQTT as the Telemetry Bus</h2>
<hr />
<p>Every deployment phase in The Construct publishes to the internal MQTT broker, the same broker handling sensor state and automation events throughout the home. If you're already running Home Assistant with an MQTT broker, this is worth wiring up.</p>
<p>The benefit is that deployment events flow into the same system you're already watching. Deployment state appears in dashboards alongside temperature and presence data. But it goes further than visibility.</p>
<p>Because the broker is a universal event bus, any service that speaks MQTT can react to deployment events. <strong>n8n</strong> (a self-hosted workflow automation tool) can subscribe to deployment topics and trigger follow-on processes automatically: running database migrations after a successful deploy, posting a notification to a channel, or kicking off integration tests. The Construct publishes the event; n8n decides what to do with it.</p>
<p>The other side is failure alerting. When a deployment breaks, the <code>rescue</code> block fires an MQTT event before re-raising the failure. That event routes to a personal push notification with the service name, the host, and the timestamp, so I know something went wrong without log diving.</p>
<pre><code class="language-plaintext">/ansible/caliper      → "Updating - 05/22/2026 14:32:01"
/deployment/caliper   → "05/22/2026 14:32:44 - ✅ Deployment of caliper completed on endor"
/deployment/caliper   → "05/22/2026 14:33:12 - 🛑 Failed to deploy caliper on endor"
</code></pre>
<p>The same system that tells you a door was left open can tell you a deployment failed.</p>
<h2>Testing: Every Role Has Molecule</h2>
<hr />
<p>If you build a shared pipeline library, test it. Each core Ansible role powering these deployment phases has a Molecule test suite that runs on every push via Gitea Actions.</p>
<p>Infrastructure code has an honest testing problem: you can't fully simulate a real deployment in CI. You can't SSH into a PVE node, mount a real NFS share, or reach the MQTT broker. Some things can only be validated against real hardware.</p>
<p>What Molecule catches is everything else, and that's still a lot. Idempotency violations (running the same role twice should produce no changes on the second run), incorrect conditionals, broken variable references, logic that works on Ubuntu but fails on Alpine. These are the errors that would otherwise surface mid-deployment against a live service. For a shared library that every service in the cluster depends on, catching them early is worth the test setup.</p>
<p>The real-hardware gaps get validated the old-fashioned way: against actual nodes before merging anything that touches those code paths.</p>
<h3>The One Rule That Doesn't Bend</h3>
<hr />
<p>Whatever pipeline you build, establish one non-negotiable: <strong>secrets never touch persistent storage.</strong> Not <code>/opt/</code>. Not <code>/etc/</code>. Not <code>/tmp/</code>. RAM only.</p>
<p>Disk is durable and RAM isn't. A reboot clears <code>/dev/shm</code>. A forensic investigation of the filesystem finds nothing. A backup of <code>/opt/caliper/</code> contains no credentials. This rule is easy to implement once in a centralized pipeline and impossible to enforce consistently across a dozen one-off scripts.</p>
<h3>Onboarding with an AI Agent</h3>
<hr />
<p>The Construct is well-documented, but documentation still requires someone to read and apply it correctly. To close that gap, I built a <strong>Claude Code skill</strong> that fully understands The Construct: the role catalog, the variable schema, The Architect's configuration model, and the naming conventions.</p>
<p>When bringing a new service into the cluster, the workflow is now: describe what the service does, whether it needs NFS, what endpoints it exposes, and what secrets it requires. The skill generates a complete <code>deploy.yml</code> from those answers, correctly wired to The Construct's roles. It also walks through exactly what to configure in The Architect (which repository to register, how to structure the variable group, how to set up the template and inventory) so the first deploy runs without touching the documentation.</p>
<p>This works because the pipeline is consistent. Because every service is configured the same way, an agent that understands the pattern can onboard any service that follows it. The centralization that makes The Construct maintainable is the same thing that makes it teachable to an AI.</p>
<h3>Build The Construct First</h3>
<hr />
<p>If there's one thing to take from this: <strong>create your centralized pipeline repo before you create your second service.</strong> Once deployment logic is scattered across individual repos, consolidating it is a project. Starting with a shared library means every service you add makes the whole system more valuable, not more complex.</p>
<p>Your pipeline should have one job: take a service from "code in a repo" to "running container on a node" in a repeatable, auditable, secure way. Everything else (observability, storage validation, health checks, audit trails) follows naturally once that foundation exists.</p>
<p>Build it once. Deploy everything with it.</p>
]]></content:encoded></item><item><title><![CDATA[Silent by Design: Blinds Automation That Disappears]]></title><description><![CDATA[The Digital Homestead · Home Automation
In my last post I went deep on the firewall that keeps my network segmented and secure. Today I'm shifting to principle 03: Simplicity; using my blinds automati]]></description><link>https://blog.wizard-networks.com/blinds</link><guid isPermaLink="true">https://blog.wizard-networks.com/blinds</guid><category><![CDATA[home automation]]></category><category><![CDATA[home automation devices]]></category><category><![CDATA[home automation systems ]]></category><category><![CDATA[Home Assistant]]></category><category><![CDATA[homeassistant]]></category><category><![CDATA[Home Assistant blinds]]></category><category><![CDATA[#SmartHomeDesign]]></category><category><![CDATA[Smart Home Design]]></category><category><![CDATA[Smart home interior design]]></category><category><![CDATA[zigbee]]></category><category><![CDATA[zigbee blinds]]></category><category><![CDATA[Jinja2 Templates]]></category><category><![CDATA[ewand]]></category><category><![CDATA[Digital Homestead]]></category><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Sun, 10 May 2026 20:25:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/163df0c8-7df1-4160-ae32-272be33db395.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>The Digital Homestead · Home Automation</em></p>
<p>In my last post I went deep on the firewall that keeps my network segmented and secure. Today I'm shifting to principle 03: Simplicity; using my blinds automation as the case study.</p>
<p>Blinds are one of those things that seem too simple to automate intelligently, and too trivial to bother building properly. That assumption is wrong on both counts. Done right, a blinds automation disappears entirely. Nobody in the house thinks about the blinds. They open when they should. They close when they should. When you come downstairs on a Saturday morning the light is already right. That's the goal.</p>
<p>Getting there took more iteration than I expected.</p>
<h3>The Hardware</h3>
<p>The blinds run on <strong>eWand</strong> Zigbee motors — one per blind, installed inside the standard wand mechanism. They pair directly into <strong>ZHA (Zigbee Home Automation)</strong> in Home Assistant and appear as switch entities. No hub, no cloud, no app.</p>
<p>The Zigbee integration was deliberate. These motors run locally. If the internet is down, the blinds still work. If eWand's servers go down (or the company disappears), nothing changes. I control the device; I don't rent access to it.</p>
<p>The one trade-off with eWand is that they expose as switches rather than proper cover entities — on or off, not a position percentage. For most blinds, that's fine. You want them open or closed, not 47% open.</p>
<blockquote>
<p>Before buying any smart home hardware I check one thing: does it work locally with Home Assistant without needing the manufacturer's cloud? eWand does. That's the whole criteria.</p>
</blockquote>
<hr />
<h3>The Architecture: One Script to Rule All Zones</h3>
<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/e017f370-df40-40ae-9c34-e8f9e0f14807.png" alt="" style="display:block;margin:0 auto" />

<p>Most automations I see hardcode device entity IDs directly into their logic. It works until you add a device, rename something, or reorganize a room — then you're hunting through YAML to patch five different automations.</p>
<p>I took a different approach. Every blind in the house carries two <strong>Home Assistant labels</strong>: <code>Blinds</code> and its zone — <code>Living Room</code>, <code>Breakfast Room</code>, <code>Dining Room</code>, <code>Primary Bathroom</code>, or <code>Sun Room</code>. The central <code>script.blinds</code> doesn't know which entities exist. It discovers them at runtime.</p>
<pre><code class="language-yaml">devices: &gt;
  {%- set data = namespace(deviceList=[]) -%}
  {% set blinds = label_entities('Blinds') %}
  {% set room = label_entities(zone) %}
  {%- for x in blinds -%}
    {%- if x in room -%}
      {%- set data.deviceList = data.deviceList + [x] -%}
    {%- endif -%}
  {% endfor %}
  {{data.deviceList}}
</code></pre>
<p>The script takes two arguments: <code>order</code> (open or close) and <code>zone</code>. It resolves the right devices dynamically, runs the command, and retries anything that didn't respond. Adding a new blind to the living room means applying two labels in Home Assistant. The automation picks it up automatically on the next run.</p>
<p>That's the kind of design that survives years of hardware changes without accumulating technical debt.</p>
<hr />
<h3>The Safety Guard</h3>
<p>Not every room should open on the same schedule. The primary bathroom has different morning timing from the living room. Some zones; such as the living room and sunroom benefit from early light regardless of the day. Others should stay closed on weekdays until people are actually moving around.</p>
<p>The script enforces this with a single template condition that gates all open commands. Three cases allow the script to proceed:</p>
<blockquote>
<p><strong>Close commands always execute.</strong> Privacy and heat control are non-negotiable. If the order is to close, it runs immediately, no conditions checked.</p>
<p><strong>Special zones have restricted schedules.</strong> The Breakfast Room and Dining Room are flagged as <code>specialScheduleZones</code>. These zones are restricted to a workday check where they only open on Weekends and Holidays</p>
<p><strong>Weekends and holidays are unblocked.</strong> When the Workday sensor is off, all zones open on their normal schedules. The system knows it's Saturday.</p>
</blockquote>
<p>One template condition, three cases, clean logic. No per-zone automation files, no duplicate guards scattered across the system.</p>
<hr />
<h3>The Reliability Loop</h3>
<p>Zigbee devices occasionally miss a command. It's rare, but a blind that doesn't respond means one blind closed out of three in the living room — which is worse than none at all.</p>
<p>The script handles this with a built-in retry loop. After sending a command, it waits ten seconds, checks whether each device is in the expected state, and re-sends to any that aren't. It keeps doing this until every device in the zone matches the target state.</p>
<pre><code class="language-yaml">repeat:
  while:
    - condition: template
      value_template: |
        {% set data = namespace(status = false) %}
        {% for device in devices %}
          {% if states(device) != order %}
            {% set data.status = true %}
          {% endif %}
        {% endfor %}
        {{data.status}}
  sequence:
    - action: switch.turn_{{order}}
      target:
        entity_id: "{{ devices }}"
    - delay:
        seconds: 10
</code></pre>
<p>The automation doesn't require a perfect mesh. It requires an eventually consistent one. That's a more honest target.</p>
<hr />
<h3>The Three Triggers</h3>
<p>Three things change the state of the blinds in this house:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/40b58d30-cb2c-49a2-9c18-b2dc0a9f4b8b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Sundown State Change</strong> is the primary driver. A <code>SunDown</code> input boolean toggles at sunrise and sunset, automatically calculated by Home Assistant based on location. At sunset, all zones close in parallel — seven simultaneous script calls, one per zone. At sunrise, the first-floor zones open. The primary bedroom is intentionally excluded from the sunrise open; that one has its own logic.</p>
<p><strong>Primary Bathroom Morning Schedule</strong> runs independently. On workdays, it opens at 8 AM. On weekends and holidays, it waits until 9 AM. The workday condition is checked at the trigger level here, since the timing difference is meaningful and zone-specific.</p>
<p><strong>Temperature Override</strong> is the one that bypasses everything else. When the outside temperature sensor crosses 78°F, all zones close. Immediately. No schedule check, no zone exceptions. Heat gain is a physical problem and it doesn't care what day of the week it is.</p>
<blockquote>
<p>The temperature close was the automation I resisted building longest. It felt unnecessary. Then I had a week in August where the AC was running hard while the blinds were open and I couldn't figure out why the house wasn't cooling. It was not unnecessary.</p>
</blockquote>
<hr />
<h3>Voice Control Without Confusion</h3>
<p>eWand motors appear as switch entities in Home Assistant — which means a voice assistant would say "turn on" and "turn off" instead of "open" and "close." That's a small thing that makes voice control feel broken.</p>
<p>The fix is an abstraction layer. Each zone gets a <strong>Cover helper</strong> built from its switch group. The Cover entity exposes "open" and "close" semantics, hides the on/off language, and maps to the underlying physical switches. Only the cover entities are exposed to voice assistants. The physical switches stay hidden.</p>
<table>
<thead>
<tr>
<th>Zone</th>
<th>Exposed Entity</th>
</tr>
</thead>
<tbody><tr>
<td>Breakfast Room</td>
<td><code>cover.breakfast_room_blinds</code></td>
</tr>
<tr>
<td>Dining Room</td>
<td><code>cover.blinds_dining_room</code></td>
</tr>
<tr>
<td>Living Room</td>
<td><code>cover.living_room_blinds</code></td>
</tr>
<tr>
<td>Primary Bathroom</td>
<td><code>cover.blinds_primary_bathroom</code></td>
</tr>
<tr>
<td>Sun Room</td>
<td><code>cover.sunroom_blinds</code></td>
</tr>
</tbody></table>
<p>"Hey Google, close the living room blinds" works the way you'd expect, because the entity it's talking to is modeled as a blind, not a light switch.</p>
<hr />
<h3>The Result</h3>
<p>The blinds in this house have been running without manual intervention for over three years. Nobody in the family thinks about them. They open in the morning, close at sunset, and close themselves when it's hot outside. Physical switches still work — walk up to any blind and twist the wand and it responds immediately, same as before automation.</p>
<p>That's what principle 03 actually looks like in practice. Not a clever system with fifteen configuration options. A system that handles the routine cases automatically, stays out of the way when someone wants manual control, and has exactly one interface for each use case: time, sun position, temperature, and voice.</p>
<p>When I evaluate whether an automation is worth keeping, I ask one question: has anyone complained about it? The blinds have never come up. That's the benchmark.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Zone Defense: How I Built a Zone‑Based Firewall That Actually Secures a Smart Home]]></title><description><![CDATA[In my last post I introduced five principles I consider non-negotiable for a well-run smart home. Today I'm going deep on principle 02: Security.
Most home network security advice stops at "use a stro]]></description><link>https://blog.wizard-networks.com/zone-based-firewall-smart-home-network-segmentation</link><guid isPermaLink="true">https://blog.wizard-networks.com/zone-based-firewall-smart-home-network-segmentation</guid><category><![CDATA[home automation]]></category><category><![CDATA[home automation devices]]></category><category><![CDATA[Smart Home Security]]></category><category><![CDATA[Smart Home Security Camera ]]></category><category><![CDATA[Network Segmentation]]></category><category><![CDATA[zone-based firewall]]></category><category><![CDATA[ZBF]]></category><category><![CDATA[unifi]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[homelabbing]]></category><category><![CDATA[home lab]]></category><category><![CDATA[homelab-setup]]></category><category><![CDATA[home lab security]]></category><category><![CDATA[home networking]]></category><category><![CDATA[Home Network]]></category><category><![CDATA[home network protection]]></category><category><![CDATA[Home Network Solutions]]></category><category><![CDATA[Home Network Setup]]></category><category><![CDATA[homenetworksecurity]]></category><category><![CDATA[iot]]></category><category><![CDATA[IoT security]]></category><category><![CDATA[iot devices]]></category><category><![CDATA[DNS Filter]]></category><category><![CDATA[DNS Filtering]]></category><category><![CDATA[Reverse Proxy]]></category><category><![CDATA[reverseproxy]]></category><category><![CDATA[SSH bastion]]></category><category><![CDATA[Home Assistant]]></category><category><![CDATA[homeassistant]]></category><category><![CDATA[Ubiquiti]]></category><category><![CDATA[Ubiquiti firewall]]></category><category><![CDATA[firewall]]></category><category><![CDATA[Firewalls]]></category><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Sun, 29 Mar 2026 23:51:14 GMT</pubDate><content:encoded><![CDATA[<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/65a413c7-9b73-4b30-afdc-edeabc694f8f.jpg" alt="Cover preview" style="display:block;margin:0 auto" />

<p>In my last post I introduced five principles I consider non-negotiable for a well-run smart home. Today I'm going deep on principle 02: Security.</p>
<p>Most home network security advice stops at "use a strong Wi-Fi password and keep your router updated." That's basic hygiene, not protection. If you run smart devices, a NAS, cameras, or a home lab alongside your everyday laptops and phones, you don't have a flat home network; you have infrastructure that deserves infrastructure‑grade controls. My solution is a Zone‑Based Firewall (ZBF): the enterprise model, scaled down to defend a homestead.</p>
<hr />
<h3>Why Zones?</h3>
<p>A traditional home network is flat. Everything: your laptop, your smart fridge, your security cameras, your kids' tablets, sits on the same network. If one device is compromised, everything else is reachable. That's not a smart home. That's an open floor plan with no doors.</p>
<p>A zone-based model divides your network into isolated segments, each with its own trust level. Traffic between zones isn't allowed by default. You define exactly what can talk to what, on which ports, in which direction.</p>
<p>The mental model is simple: think of your home network as a building. Each zone is a room. The firewall is the set of doors and locks between them. Some rooms are open to everyone. Some require a key. Some, like the server room, have a locked door, a keypad, and a camera.</p>
<p>Your smart lightbulb doesn't need to be in the same room as your NAS.</p>
<hr />
<h3>The Trust Hierarchy</h3>
<p>Before building rules, you need to establish trust levels. Not all zones are equal. Here's how I think about mine, from least trusted to most:</p>
<p><strong>Uncontrolled: The Internet</strong> Outside your network entirely. Zero trust. Zero policies. Everything inbound is assumed hostile until proven otherwise.</p>
<p><strong>Unmanaged: Guest Zone</strong> Devices you don't own or control. They get internet access and nothing else. No visibility into your internal zones, no route to your services.</p>
<p><strong>Managed: Everything else</strong> Your zones with defined, enforced policies. Even within managed zones, the level of access varies significantly.</p>
<hr />
<h3>My Six Zones</h3>
<p>Here's how my network is segmented:</p>
<p><strong>Service Zone</strong> is the core of my infrastructure: reverse proxies, DNS, an SSH bastion, storage services, and the VMs and containers that run everything. It's the most locked-down zone in terms of what can reach it.</p>
<p><strong>Control Zone</strong> houses the physical infrastructure: the router, switches, and NVR. Only specific management traffic reaches here.</p>
<p><strong>Camera Zone</strong> contains one thing: cameras. They can talk to the NVR in the Control Zone and nowhere else.</p>
<p><strong>Smart Zone</strong> is isolated to Home Assistant and the IoT devices it manages: Zigbee, Z-Wave, and BLE. HA can reach the Service Zone for DNS and backups. Nothing in the Smart Zone can reach User or Control.</p>
<p><strong>User Zone</strong> is where everyday devices live — laptops, phones, desktops. They can reach the Service Zone for DNS, the reverse proxy, and the bastion. They can reach Home Assistant's web UI. That's it.</p>
<p><strong>VPN Zone</strong> is how I access everything remotely. Two VPN connections, one site-to-site to a family member's home and one for my laptop, both land in the same zone and get the same access as the User Zone.</p>
<p><strong>Guest Zone</strong> is the red-bordered outlier. Devices get internet access and nothing else, isolated from every other zone entirely. But it goes further than that: guest devices are also isolated from each other within the zone. A device on your guest network can't see, probe, or communicate with any other device on that same network. No exceptions.</p>
<hr />
<h3>The Diagram</h3>
<img src="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/87a7150c-6c18-49d1-80e8-484bb0a416e6.png" alt="" style="display:block;margin:0 auto" />

<p>The vertical ZBF policy points in the center are the key. Every arrow, every traffic flow, passes through one of those enforcement points. There is no zone-to-zone communication that doesn't go through the firewall. That's the whole point of the model.</p>
<p>Arrow direction in the diagram indicates the session originator. Return traffic is handled automatically by UniFi's stateful inspection. You define the rule once in one direction, and the return path is managed for you.</p>
<hr />
<h3>Application Intermediaries: The Pattern That Makes It Work</h3>
<p>Zones define the boundaries. Application Intermediaries are what enforces them at the service level.</p>
<p>Rather than letting zones talk directly to each other, every cross-zone service request goes through an intermediary: a reverse proxy, a bastion host, a storage endpoint, that terminates the inbound session, enforces authentication and encryption, and opens a new connection to the backend. The client never reaches the destination directly.</p>
<p>Application Intermediaries are a big enough topic to deserve their own post — I'll go deep on the architecture, the specific tools, and the configuration patterns in a future installment.</p>
<hr />
<h3>UniFi Implementation Notes</h3>
<p>I run all of this on a UniFi Dream Machine Special Edition. A few things worth knowing if you're implementing this on UniFi:</p>
<p><strong>Zones are configured per-network.</strong> You create the zone in UniFi, assign a network to it, then define policies between zones. The firewall rules are written at the zone level, not the interface level, which is what makes ZBF cleaner than traditional ACL-based approaches.</p>
<p><strong>UniFi auto-generates return rules.</strong> When you create an allow rule from Zone A to Zone B, UniFi automatically creates the matching return rule in the reverse direction. You don't write return rules manually. This is a significant quality-of-life improvement over managing stateless rules by hand.</p>
<p><strong>Host lists and port lists are your best friends.</strong> Rather than writing individual rules for each IP, define named host lists (DNS-Hosts, SSH-Hosts, Proxy-Hosts) and port lists (DNS-Ports, SSH-Ports). Rules reference the lists. When an IP changes, you update the list once and every rule that references it stays correct.</p>
<p><strong>Naming conventions matter.</strong> I follow a strict format: <code>Action-SourceZone-DestinationZone-Service</code>. For example: <code>Allow-VPN-Service-SSH</code>. Every rule name tells you exactly what it does without opening it. This matters when you have 30+ rules and need to audit them quickly.</p>
<p><strong>VPN is a zone, not a network.</strong> UniFi treats all VPN connections as a single VPN zone. If you have multiple VPN clients or site-to-site connections, they all share the same zone policy. Plan your access accordingly — you can't easily give different VPN connections different permissions within the same zone.</p>
<p><strong>DNS filtering is a first-class security control.</strong> My DNS servers aren't just resolvers. They're ad-blocking DNS servers that silently drop requests to known tracking and telemetry domains. This matters most for IoT devices. Most smart home hardware phones home aggressively: usage telemetry, advertising identifiers, sometimes audio snippets. DNS filtering lets devices reach the update servers they actually need while dropping everything else before a connection is ever established. The device doesn't get an error. The request just disappears.</p>
<p><strong>Region blocking at the WAN interface.</strong> As a final layer, I've configured geographic blocking on the WAN interface. Traffic originating from regions I have no legitimate reason to communicate with is dropped before it reaches the firewall policy layer. It doesn't replace proper zone rules. A determined attacker can route around it. But it significantly reduces the noise floor of inbound probes and attack traffic that every internet-connected network sees constantly.</p>
<hr />
<h3>The Result</h3>
<p>What does all of this actually buy you?</p>
<p>A compromised IoT device in the Smart Zone can't reach your laptops, your NAS, or your cameras. It can reach Home Assistant, because you explicitly allowed that, and it can reach the internet for updates. That's it.</p>
<p>A guest on your Wi-Fi gets internet and nothing else. They can't see your NAS, your cameras, your servers, or your smart home. Not because you trust them less than family, but because the network enforces it automatically, regardless of intent.</p>
<p>Your administrative access has exactly one entry point. That bastion is hardened, logged, and audited. There's no second path in.</p>
<p>And when something does go wrong, a misconfigured container, a device behaving unexpectedly, an IPS alert, the blast radius is contained to the zone it started in.</p>
<p>That's not paranoia. That's just how you build infrastructure that you can trust.</p>
<p>There's always more to do. Security isn't a state you reach. It's a practice. Writing this post alone surfaced a handful of improvements I hadn't gotten around to yet, now sitting in my backlog. That's the nature of it. You build the foundation, you document what you have, and the act of explaining it clearly tells you exactly where the gaps are.</p>
<hr />
<p><em>Next up I'll shift gears to principle 03: Simplicity. I'll walk through my blinds automation as a real-world example of what it looks like when automation gets out of the way and just works.</em></p>
]]></content:encoded></item><item><title><![CDATA[Five Unbreakable Rules for a Smart Home]]></title><description><![CDATA[The Digital Homestead · Home Automation
Most smart home setups fail not because of bad hardware, but because they neglect the fundamentals. After years of designing and refining my own system, I've se]]></description><link>https://blog.wizard-networks.com/five-unbreakable-rules-for-a-smart-home</link><guid isPermaLink="true">https://blog.wizard-networks.com/five-unbreakable-rules-for-a-smart-home</guid><category><![CDATA[home automation]]></category><category><![CDATA[home automation devices]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[#selfhosted]]></category><category><![CDATA[#InfrastructureAsCode]]></category><category><![CDATA[Devops]]></category><category><![CDATA[smarthome]]></category><category><![CDATA[smart home]]></category><category><![CDATA[smart home automation]]></category><category><![CDATA[smart home solutions ]]></category><category><![CDATA[Smart Home Market Scope]]></category><dc:creator><![CDATA[Ryan Rodrigue]]></dc:creator><pubDate>Sat, 28 Mar 2026 00:32:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c2191530a9b81e3aee5921/0b5d96a9-a05c-40a9-8bd6-bd4850e84d63.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>The Digital Homestead · Home Automation</em></p>
<p>Most smart home setups fail not because of bad hardware, but because they neglect the fundamentals. After years of designing and refining my own system, I've settled on five non‑negotiable principles.</p>
<p>There's a common trap with home automation: you start with a few smart bulbs, then a thermostat, then some door sensors and before long you're juggling 40 devices across six apps, including three that depend on cloud services that went offline at 2 a.m. last Tuesday.</p>
<p>That chaos is the default state of the modern smart home. To avoid it, you have to stop thinking about gadgets and start thinking about systems.</p>
<hr />
<h2>01 · Reliability</h2>
<h3>The Foundation</h3>
<p>A smart home that doesn't work when you need it is worse than no smart home at all. Imagine your front door lock failing on a cold night because your Wi-Fi router rebooted or a cloud server in another state went offline. That’s not a hypothetical; it’s the inevitable result of a poorly planned system.</p>
<p>Reliability starts with moving away from a Wi-Fi-only mindset. While Wi-Fi is great for high-bandwidth tasks, it’s a bottleneck for home automation. I’ve built a resilient environment by offloading my sensors and critical controls to dedicated mesh protocols:</p>
<blockquote>
<p>Zigbee: This is my workhorse. I run two separate Zigbee networks to manage a dense mesh of contact, motion, and mmWave presence sensors, along with non-invasive vibration monitoring for my generator.</p>
<p>Z-Wave: I reserve this for critical controls like fan speeds to keep traffic off the 2.4GHz spectrum.</p>
<p>BLE: For niche devices like SwitchBot blinds, I use an ESP32 BLE proxy to bridge them without range limits.</p>
</blockquote>
<p>By moving these off the Wi-Fi, my automations don't care if the internet hiccups. For truly critical systems—climate control and security—I always build in fail-safes. A good smart home doesn’t just "break"; it degrades gracefully.</p>
<hr />
<h2>02 · Security</h2>
<h3><em>Non-negotiable</em></h3>
<p>In a Digital Homestead, you don't trust the device manufacturer to secure your home; you secure the environment around the device. My security posture is built on a <strong>Zone-Based Firewall (ZBF)</strong> running on a <strong>UniFi Dream Machine Special Edition (UDM SE)</strong>, splitting my infrastructure into six isolated zones: <strong>Control, Smart, Services, User, Camera, and Guest.</strong></p>
<p>To manage access to these zones, I follow three strict rules:</p>
<blockquote>
<p>Reverse Proxies for Web Traffic: All 443 (HTTPS) traffic hitting my Services network is funneled through reverse proxies. This gives me a single point of entry to manage SSL and access logs without exposing individual application ports to the internet.</p>
<p>The Zero-Direct-SSH Rule: I have removed direct SSH access from my internal services. Instead, all administrative traffic must pass through a hardened <strong>Bastion Host</strong>. It’s a dedicated gatekeeper that enforces modern ciphers and key-only authentication.</p>
<p>DNS-Filtered Internet Access: While my Smart devices have internet access, they don't have <em>free</em> access. All traffic is routed through a <strong>DNS-based ad blocker</strong>. This allows devices to reach necessary update servers while silently dropping the aggressive telemetry and tracking requests that most IoT hardware tries to phone home with.</p>
</blockquote>
<hr />
<h2>03 · Simplicity</h2>
<h3><em>Often overlooked</em></h3>
<p>The best automation is the one nobody notices. If your family members are constantly fighting with automations, or asking you to "turn off the smart stuff," you've overcomplicated it.</p>
<p>My rule: automations should work silently in the background, triggered by presence, time, or sensor data — not by someone having to open an app. The lights in my office turn on when I sit down and off when I leave. Nobody thinks about it. That's the goal.</p>
<blockquote>
<p>Just as important: every automation needs a simple physical override. Switches still work as switches in my house. So anyone can control anything without needing a phone or asking Alexa. Automation should enhance control, not replace it.</p>
</blockquote>
<p>Before I add any new automation, I ask myself: does this make something easier for everyone, or just more complex? If it requires explanation, it usually doesn't get built.</p>
<hr />
<h2>04 · Interoperability</h2>
<h3><em>Future-proofing</em></h3>
<p>The smart home industry is littered with the corpses of proprietary ecosystems. Wink. SmartThings hubs. Insteon. Each one left users stranded when the company pivoted or shut down. Betting your home's infrastructure on a closed ecosystem is a risk not worth taking.</p>
<p>I bias heavily toward open, device-agnostic standards: <strong>Zigbee</strong> and <strong>Z-Wave</strong> are my current pillars because they are mature, community-maintained, and run entirely locally. <strong>Home Assistant</strong> serves as my central nervous system because it integrates with almost everything and ensures I own my data.</p>
<blockquote>
<p>Before buying any device, I check whether it works locally with Home Assistant without needing the manufacturer's cloud. If the answer is no, I look for an alternative. There almost always is one.</p>
</blockquote>
<hr />
<h2>05 · Supportability</h2>
<p><em>The long game</em></p>
<p>Your smart home is infrastructure. Like any infrastructure, it needs to be maintainable — by you, and ideally by someone else if you're ever unavailable. That means good documentation, logical naming conventions, and avoiding setups so clever that only you understand them.</p>
<p>Local control is central to this. If my internet goes down, my automations still run. If Home Assistant's servers go down (they don't have any — it's self-hosted), nothing changes. I control the entire stack.</p>
<blockquote>
<p>I document everything in a private wiki: network diagrams, architectural decisions, and the logic behind every automation. But the real game-changer has been moving my documentation into a <strong>local git repo</strong> and using <strong>AI agents</strong> to help maintain it.</p>
</blockquote>
<p>Avoid vendor lock-in wherever possible. Own your data. Run things locally. And when you do use cloud services, make sure there's a local fallback.</p>
<hr />
<p>These five principles didn't come from reading a guide — they came from breaking things and rebuilding them. Every shortcut I took early on eventually cost me more time to fix than it saved.</p>
<p>In future posts I'll go deeper on each of these: the specific hardware I use, how my network is segmented, and the automations that actually stuck after years of iteration.</p>
]]></content:encoded></item></channel></rss>