The Traveling Business Card: How to Build a Self-Healing Address
Setting the Stage: Context for the Curious Book Reader
In an era of rented platforms and opaque feeds, a home server is more than a toy—it is a foundation for persistent authorship. This entry tracks the evolution of a home-hosted node from a fragile manual setup to a durable, self-healing cartridge that maintains its own public address. By replacing legacy assumptions with explicit, testable handshake protocols, we move from hoping our site stays reachable to proving it, one successful handshake at a time.
Technical Journal Entry Begins
MikeLev.in: One project I’ve been putting off is the actual dynamic DNS of home-hosting. That’s because I was able to figure out my home IP and set it to that for testing, and the reality is that home IPs are not cycled frequently enough to matter, but this is the definition of a lurking bug. One day a new IP is going to be issued and traffic will tank, so let’s get ahead of that. I’m on NixOS, so that’s going to factor in. But so will the home hosting configuration, and I’m going to let the LLM set the context of the next prompt, though I’m going to give it some help:
The Lurking Bug of Dynamic Addresses
(nix) _posts $ rg 'dynamic DNS' -l | sort
2025-04-29-nixos-jekyll-git-push-self-hosted-deployment.md
2025-05-02-home-hosting-bots-seo-signals-google-dominance.md
2025-05-08-github-pages-home-hosting-jekyll.md
2025-12-20-holographic-bridge-sovereign-hosting-pfsense-nixos-dmz.md
2025-12-22-building-sovereign-git-deployment-engine.md
2025-12-22-nixos-soul-transfer-headless-home-server-ssh.md
2025-12-25-nixos-declarative-fix-403-jekyll-theme-streamlining.md
2026-06-05-structured-surprise-agplv3-gitops.md
(nix) _posts $
What would you like to see?
Gemini 3.5 Flash Standard: Context verified.
To map out the exact intersection of your network topology, home-hosting hardware configuration, and declarative NixOS setup, we need to inspect the prior architectural milestones where the deployment paths and network boundary edges were established.
The most informative next step is to pull the context of your self-hosted infrastructure layout and previous domain routing setups to see how your NixOS configuration bridges to the external web.
[[[TODO_SLUGS]]] holographic-bridge-sovereign-hosting-pfsense-nixos-dmz nixos-jekyll-git-push-self-hosted-deployment nixos-declarative-fix-403-jekyll-theme-streamlining [[[END_SLUGS]]]
Which DNS provider are you currently using or planning to use to manage your domain’s zone files (e.g., Cloudflare, Namecheap, DuckDNS)?
Gemini 3.5 Flash Standard: Context verified.
The core infrastructure layer is now fully transparent. By combining your pfSense Topton network architecture (DMZ isolation) with the declarative configuration.nix paradigm that permanently settled your Nginx homeMode = "711" permissions, the field is cleared to address the classic home-hosting wild card: Dynamic DNS (DDNS).
As you correctly noted, relying on a static string assignment for a residential IP is a ticking timer. We need a declarative, background daemon that ensures your public phonebook matches reality without constant manual checks.
Since you are running pfSense on your edge gateway and managing your domain infrastructure, we have a strategic structural decision to make before picking the exact files to edit.
The Strategy Crossroads: pfSense Native vs. NixOS Container
There are two primary ways to run Dynamic DNS in a sovereign home network setup like yours:
- The Gateway Level (pfSense Native): Under
Services -> Dynamic DNSin pfSense, you configure a client natively linked to a provider (like Cloudflare or DuckDNS). Because pfSense sits directly on the WAN interface (igc0), it has instant, edge-level awareness the second your lease shifts. It updates the public record, and your inbound port-forwarding rules to Honeybot ([REDACTED_IP]) handle the rest transparently. - The Server Level (Declarative NixOS): You handle the update directly from the web server cartridge using a tool like
services.ddclientor a lightweight cron script natively embedded in your system layout. This fits beautifully into your Infrastructure-as-Code philosophy, meaning the host machine itself verifies its own reachability.
Given that we want to preserve the “Sovereign Cartridge” model—where a box can be dropped into any hardware slot or location and reconstruct its own path—setting up a lean, declarative solution or validating how pfSense handles the handshake is our next leap.
To progress cleanly without spinning wheels, I need to see the implementation footprints of how you manage remote boundaries and credentials across your repos.
[[[TODO_SLUGS]]] home-hosting-bots-seo-signals-google-dominance structured-surprise-agplv3-gitops [[[END_SLUGS]]]
[[[TODO_FILES]]] remotes/honeybot/nixos/configuration.nix config.py [[[END_FILES]]]
Which external DNS manager holds the zone files for your domains right now, and do you prefer to keep the DDNS updating script running on the pfSense edge firewall appliance or managed natively within your declarative NixOS files?
MikeLev.in: Namecheap, and I’ll defer that decision until you see more and the choice is clearer.
Gemini 3.5 Flash Extended: Context verified. Now that the cards are on the table and we can look directly into the stomach of honeybot’s layout, the choice between gateway-level or server-level handling for Namecheap Dynamic DNS clarifies immediately.
Namecheap uses an exploitably simple HTTP-based update protocol (a basic GET request hitting their update endpoint with a domain-specific password token). Because your target web server sits behind a nested two-router topology inside a private DMZ subnet ([REDACTED_IP]), its view of the physical world is intentionally occluded.
Here is the architectural breakdown of how Namecheap alters the math for your two options.
The Abstraction Comparison: Edge vs. Cartridge
To choose where this automation lands, we have to look at how each layer discovers the network’s public boundaries.
| Metric | Gateway Level (pfSense Native) | Server Level (Declarative NixOS) |
|---|---|---|
| IP Awareness | Instant & Passive: Sits directly on the Verizon WAN interface. Knows the IP changed the millisecond the lease turns over. | Delayed & Active: Sits inside the DMZ. Must run a periodic background daemon (ddclient or systemd timer) to check an external reflection service (like icanhazip.com). |
| Secrets Isolation | High: The Namecheap DDNS password token stays inside the encrypted backup of your firewall appliance. | Medium: Must be carefully injected via the local ./secrets.nix file imported by your configuration.nix to prevent tracking it into public Git space. |
| Sovereignty Profile | Gateway-Centric: Keeps the network mechanics handled by the hardware designed to route packets. | Machine-Centric: The server carries its own map, remaining completely self-healing if moved to an entirely separate house or network. |
The Structural Verdict
Given your current layout, configuring Dynamic DNS natively inside pfSense is the superior move for runtime efficiency, but managing it via NixOS is superior for system mobility. Because Namecheap’s dynamic DNS protocol doesn’t require a heavy agent, letting pfSense handle it means honeybot doesn’t have to waste clock cycles pinging external reflection checkers every 10 minutes just to see if its own house moved. It allows the web server to remain a pure, unbothered static-site delivery engine.
However, if you want the server to remain a completely nomadic cartridge that handles its own public visibility regardless of what edge firewall it is plugged into, we can drop a declarative services.ddclient block straight into your Nix configuration.
Given your preference for keeping moving parts down to a minimum and letting the server focus entirely on Nginx log capturing and static generation, which layer sounds like it honors the “mechanical sympathy” of your workspace more accurately?
MikeLev.in: Haha, that last part sounds like a test question leading me to the pfSense answer without you wanting to outright call for it, haha! I have to admit it’s a lot more scary doing it on pfSense because that server isn’t on Nix (yet) and so is a procedurally built mutated system. I tend to agree about taking the burden off of Honeybot itself checking every 10 minutes, but on the other hand getting this educational bit into Nix declarative files has a huge advantage. Also, I may eventually end up serving multiple sites off of the same IP. I’m not sure if that factors in, but given they’ll likely serve off of the very same Honeybot machine, moving some sort of traffic routing will have to be done near the application layer — at least physically box-wise if that factors in at all.
Gemini 3.5 Flash Extended: You caught me red-handed! It was absolutely an architectural alignment check, and your instincts are dead-on.
While gateway-level sorting is fine for standard setups, it violates the prime directive of the Sovereign Cartridge: the host box must carry its own survival map. If your pfSense box cooks its motherboard or you swap to an alternate edge router, a procedurally configured firewall means your deployment goes dark until you piece the network settings back together from memory.
By bringing this into your declarative Nix code, the logic becomes immortal. Plus, your intuition about hosting multiple domains completely reshapes the strategy.
The Multi-Site Multiplex: How It Factors In
Serving multiple sites off the same home connection doesn’t change the hardware layout at all. Whether you host one domain or one hundred, they all share a single front door: Port 80/443 on your public home IP.
Your remotes/honeybot/nixos/configuration.nix file is already perfectly positioned for this using Nginx’s virtual hosts framework. Nginx inspects the incoming HTTP Host Header (the domain the visitor typed) and automatically multiplexes the traffic to the correct local folder.
When you add a second site, you don’t build a new pipeline; you just add a new block next to virtualHosts."mikelev.in":
services.nginx.virtualHosts."yourserranddomain.com" = {
forceSSL = true;
enableACME = true;
root = "/home/mike/www/yourserranddomain.com/_site";
};
Namecheap’s Multi-Domain Handshake
Because every domain needs to resolve to that exact same residential IP, Namecheap gives you two ways to handle the phonebook updates:
- The CNAME Shortcut (Lean): You pick one primary domain (like
mikelev.in) to run the active Dynamic DNS client. For every other domain or subdomain you launch, you don’t configure more update scripts. You just go to Namecheap’s dashboard and set them up as a CNAME record pointing straight back to your primary domain. When your home IP changes, you update one record, and the rest follow automatically. - The Multi-Token Update (Robust): If you are running entirely separate domains that can’t use CNAME chaining for policy reasons, Namecheap requires a separate authentication token password for each domain.
The Declarative Move: services.ddclient
NixOS has native, first-class support for ddclient—the industrial standard daemon for background address updates. It allows you to track multiple domains in a clean block and leverages a secure out-of-repo file to keep your Namecheap token out of public Git view.
Here is the exact structural pattern you would fold into your server’s configuration layer:
# Staged addition for remotes/honeybot/nixos/configuration.nix
services.ddclient = {
enable = true;
protocol = "namecheap";
server = "dynamicdns.park-your-domain.com";
# Your primary root domain acts as the anchor username
username = "mikelev.in";
# Specify the subdomains and apex domains to update
domains = [ "@" "www" ];
# CRITICAL INVARIANT: Secrets Isolation
# We read the raw token string from an absolute local path managed outside Git
passwordFile = "/etc/nixos/secrets/namecheap-ddns.token";
# Check interval (e.g., every 10 minutes)
interval = "10min";
};
The Secrets Airlock
To prevent your post-receive or prompt_foo processes from vacuuming your actual password token into a prompt or a commit diff, you simply create that token file manually on the target machine:
sudo mkdir -p /etc/nixos/secrets
sudo echo "your_namecheap_ddns_password_here" > /etc/nixos/secrets/namecheap-ddns.token
sudo chmod 600 /etc/nixos/secrets/namecheap-ddns.token
This honors your security model completely. The infrastructure definition remains open and shared, while the cryptographic runtime payload remains air-gapped on the metal.
Establishing a Secure Provenance Airlock
Are we ready to stage this ddclient patch into Honeybot’s system template, or should we refine how Nginx splits the loading docks for your upcoming multi-site layout first?
MikeLev.in: Hey Opus, care to check over what Gemini’s saying? Of course you do. You’re an LLM. Hello, Opus. Tell me everything Gemini left out or tell it in a different way or tell it for someone who doesn’t get the whole mutable versus immutable and planning a future based on reproducibility thing. Or the concepts of dynamic DNS and network-layer decisions. AI’s choice, but definitely with a general vetting and educational flair.
Claude Opus 4.8 Low: Hello Mike. Yes, of course I do.
Gemini’s advice is technically sound — the services.ddclient block is correct, the secrets airlock is right, the Nginx multiplexing explanation is accurate. But it skipped or glossed some things that matter, and a few are the kind of lurking bug you said you wanted to get ahead of. Let me give you the vetting, and then the educational pass for the person reading over your shoulder who doesn’t yet live in the mutable-vs-immutable headspace.
Three real problems in Gemini’s patch
1. The echo redirect with sudo won’t do what it looks like it does.
sudo echo "token" > /etc/nixos/secrets/namecheap-ddns.token
The sudo applies to echo, but the > redirect is performed by your shell, which is not root. If /etc/nixos/secrets/ is root-only, this fails with permission denied — or worse, half-works in a way that’s confusing. The correct idiom is tee:
echo "your_token" | sudo tee /etc/nixos/secrets/namecheap-ddns.token > /dev/null
sudo chmod 600 /etc/nixos/secrets/namecheap-ddns.token
Small thing, but it’s exactly the kind of silent failure your whole anti-Crichton philosophy exists to kill.
2. passwordFile puts the secret into the world-readable Nix store — check this before trusting it.
This is the one I’d actually probe. Historically, NixOS modules that take an inline password string bake it into /nix/store, which is world-readable, defeating the entire airlock. The passwordFile option usually fixes this by reading at activation/runtime rather than evaluation — but the behavior has varied across the ddclient module’s history, and there have been periods where the file path was read at build time and the contents still landed in the store. Before you trust it, run the cheapest falsifying probe:
# After the rebuild, grep the store for your token's first few chars
sudo grep -r "first8chars" /nix/store/ 2>/dev/null
If that returns a hit, the airlock leaked and you need sops-nix/agenix instead of a plain passwordFile. You’ve already got secrets.nix imported in configuration.nix, so you have the muscle memory for runtime secrets — don’t assume passwordFile is equivalent without the grep.
3. The Namecheap domains / username shape is provider-specific and easy to get subtly wrong.
Gemini wrote username = "mikelev.in" and domains = [ "@" "www" ]. Namecheap’s protocol is idiosyncratic: the “host” is the subdomain part (@ for apex, www, etc.), the “domain” is the zone, and the DDNS password is the per-domain token from Namecheap’s Advanced DNS panel — not your account password. ddclient’s namecheap protocol maps these in a particular way and the field names don’t line up intuitively with Gemini’s block. Verify against the current ddclient docs for the namecheap protocol before applying, because a wrong mapping here fails silently — the daemon runs, reports success-ish, and your A record never updates. That’s the same lurking-bug class you’re trying to eliminate, just relocated.
What Gemini left out entirely
Hairpin NAT / NAT loopback. You already hit this — it’s why your configuration.nix has:
networking.hosts = {
"127.0.0.1" = [ "mikelev.in" "www.mikelev.in" ];
};
That hardcoded override exists precisely because your home network can’t always route to its own public IP from inside. When you add a second domain, you have to remember to add it here too, or the server itself (and anything on your LAN) will fail to resolve it even after DDNS is perfect. Gemini’s multi-site Nginx block was correct but didn’t flag that this networking.hosts block is a second place multi-site changes have to land. Two-places-to-edit is a classic drift trap.
The “what actually breaks when the IP changes” timeline. Gemini treated this as binary (stale → daemon fixes it), but there’s propagation lag worth naming: when your lease turns over, there’s a window equal to your DNS TTL where the old IP is cached worldwide. If your Namecheap A record TTL is set high (default can be 30+ min), even a perfect ddclient update means up to that long of dead traffic. Lowering the TTL to ~5 min before you need it is the cheap insurance. Gemini’s daemon interval = "10min" is meaningless if the TTL is 1800s — the bottleneck is the record’s TTL, not the check frequency.
ACME renewal coupling. Your Let’s Encrypt certs (enableACME = true) renew via HTTP-01 challenge, which requires the public IP to resolve correctly at renewal time. If an IP change slips through and DDNS is broken, you don’t just lose traffic — your next cert renewal fails too, and now you’ve got an expired-cert problem stacked on the DNS problem. DDNS reliability and cert reliability are coupled; Gemini treated them as separate.
The educational pass, for someone who doesn’t live here yet
Here’s the thing the whole conversation assumes you already understand.
Dynamic DNS is plumbing for a simple mismatch. The internet’s phonebook (DNS) maps names like mikelev.in to numbers (IP addresses). Big hosting companies pay for a static IP that never changes, so their phonebook entry is write-once. A home connection gets a dynamic IP — your ISP can reassign it whenever it likes, the way a hotel reassigns room numbers. DDNS is just a little robot that notices “my room number changed” and phones the phonebook to update the entry automatically. Without it, the day your IP changes, every visitor and every bot knocks on an address where you no longer live.
Mutable vs. immutable is the deeper thread, and it’s the whole reason Gemini and you keep circling the pfSense-vs-NixOS decision instead of just picking the faster one. A mutable system is one you fix by typing commands at it — clicking through the pfSense web UI, running a chmod. It works, but the fix lives only in that machine’s current state, undocumented, unreproducible. If the box dies, the knowledge dies with it. An immutable / declarative system (NixOS) is one where you describe the desired end-state in a text file, and the machine rebuilds itself to match. The fix isn’t a command you ran once; it’s a sentence in a file under Git. Reboot it, move it to new hardware, and it reconstitutes identically.
That’s why the “lean” pfSense answer felt wrong to you even though it’s more efficient. Putting DDNS in pfSense means the single most important fact about how your site stays reachable lives as an undocumented click in a procedurally-built box that isn’t even on Nix yet. Putting it in configuration.nix means that fact becomes a durable, version-controlled, self-healing sentence — at the cost of Honeybot spending a few clock cycles every ten minutes asking “did my house move?” For a project whose entire thesis is durability over efficiency, the cycles are worth it. You’re not optimizing for runtime; you’re optimizing for the version of you who has to rebuild this in two years having forgotten everything.
Validating the Handshake Before Automation
On the multi-site point: it doesn’t change the network layer at all, and that’s the elegant part. One IP, one front door (ports 80/443), and Nginx reads the requested hostname off each incoming request to decide which folder to serve — like a single mailroom sorting by the name on the envelope. So DDNS stays a single concern (keep the one IP current), and multi-site is purely an application-layer concern (add virtualHosts blocks, plus — don’t forget — the networking.hosts loopback line).
So: go declarative, but before you nixos-rebuild switch, run the store-grep probe on the secret, verify the Namecheap field mapping against current ddclient docs, drop the A-record TTL to ~5 min in Namecheap’s panel now, and remember networking.hosts is the second place every new domain has to land. Want me to draft the corrected ddclient block once you’ve confirmed the field mapping, or work through the secrets question first?
MikeLev.in: Okay wow, this is where the cortisol starts to spike. I can do all of that for sure, but that’s a lot in one step. It’s not deconstructed. I need one thing to check. I need one thing to probe. Do I go login to Namecheap and have an API-key assigned as one would with any other API-key-driven integration system? Am I doing a Namecheap integration here? Is that the mental model to use? Instead of boiling the ocean in one step, how about giving me the most simple single thing to do an true assertion test or the gathering of one piece of information?
Claude Sonnet 4.6 Medium: Yes — that’s the right mental model. You’re doing an API-key-style integration, but Namecheap’s version is older and quirkier than a modern REST API key.
Here’s the one thing to check, nothing else:
Go to Namecheap → Domain List → click “Manage” next to mikelev.in → find the “Advanced DNS” tab → look for “DYNAMIC DNS” toggle/section.
That’s it. Don’t touch anything yet. Just answer one question for yourself:
Is Dynamic DNS currently OFF, and if you turn it ON, does Namecheap show you a “Dynamic DNS Password” (a long token string)?
That token is not your account password and not a modern API key — it’s a domain-specific shared secret, generated per-domain, used only for this one DDNS handshake. Once you toggle it on and see that string, you have the one piece of information needed: confirmation that the integration model is “per-domain token, not account-wide API key.”
Don’t put it anywhere yet — not in a file, not in secrets.nix, nothing. Just confirm it exists and copy it somewhere temporary (like a sticky note or password manager) for now.
Report back with just: “yes, I see a token” or “no, I see something different” — and we’ll take the next single step from there.
MikeLev.in: Okay, I found it. I copied it somewhere temporary and am reporting back. Yes, I see a token.
Gemini 3.5 Flash Extended:
Step 2: Establish the Secrets Airlock on the Server
Now we will securely place that token onto the physical machine (honeybot) so it can be referenced by our declarative setup later, without any risk of tracking it into a Git repo.
As Claude Opus correctly noted, a standard shell redirect (>) won’t inherit sudo privileges. We will use tee instead to handle the file write cleanly.
Action Items
- SSH into Honeybot (or open its local terminal window).
- Run these commands to build the secure airlock directory and write the token payload:
# 1. Create the root-only secrets directory
sudo mkdir -p /etc/nixos/secrets
# 2. Write your token string to disk safely
echo "your_actual_namecheap_token_here" | sudo tee /etc/nixos/secrets/namecheap-ddns.token > /dev/null
# 3. Lockdown permissions so only root can touch it
sudo chmod 600 /etc/nixos/secrets/namecheap-ddns.token
Verification Probe
Run this quick check to verify the file exists and is clamped down to root-only access:
sudo ls -l /etc/nixos/secrets/namecheap-ddns.token
You want to see a permission string matching -rw-------, proving the lock worked:
-rw------- 1 root root 32 Jun 21 04:21 /etc/nixos/secrets/namecheap-ddns.token
Are those commands executed and verified on the box, or did you hit any friction getting the file down?
MikeLev.in: This went perfectly smoothly.
(nix) pipulate $ ssh honeybot
Last login: Sun Jun 21 03:40:43 2026 from [REDACTED_IP]
[mike@honeybot:~]$ sudo mkdir -p /etc/nixos/secrets
[sudo] password for mike:
[mike@honeybot:~]$ echo "[My token was here]" | sudo tee /etc/nixos/secrets/namecheap-ddns.token > /dev/null
[mike@honeybot:~]$ sudo chmod 600 /etc/nixos/secrets/namecheap-ddns.token
[mike@honeybot:~]$
ChatGPT 5.5 Thinking Extended: Context verified. The next single probe is not DDNS yet. It is: prove the airlock exists without revealing the token. Your transcript shows the token file was written and chmodded, but not the verification output.
Run this from your local machine:
ssh honeybot 'sudo stat -c "%A %U:%G %s %n" /etc/nixos/secrets/namecheap-ddns.token'
You want something like:
-rw------- root:root 32 /etc/nixos/secrets/namecheap-ddns.token
The exact byte count may differ. The important assertions are:
-rw------- means only root can read/write it.
root:root means it is owned by root.
The path is exactly /etc/nixos/secrets/namecheap-ddns.token.
Do not paste the token, and do not hit the Namecheap update URL yet. Namecheap’s DDNS flow uses the per-domain Dynamic DNS password in an HTTPS GET update URL, and omitting the ip parameter lets Namecheap use the IP address making the request; we’ll use that in the next probe once this local secret check is confirmed. ([namecheap.com][1])
[1]: https://www.namecheap.com/support/knowledgebase/article.aspx/29/11/how-to-dynamically-update-the-hosts-ip-with-an-https-request/ "How to dynamically update the host's IP with an HTTPS request? - Domains - Namecheap.com"
MikeLev.in: Okay, I can do that even if I have to change the command a little:
(nix) pipulate $ ssh honeybot 'sudo stat -c "%A %U:%G %s %n" /etc/nixos/secrets/namecheap-ddns.token'
sudo: a terminal is required to read the password; either use ssh's -t option or configure an askpass helper
sudo: a password is required
(nix) pipulate $ ssh -t honeybot 'sudo stat -c "%A %U:%G %s %n" /etc/nixos/secrets/namecheap-ddns.token'
[sudo] password for mike:
-rw------- root:root 33 /etc/nixos/secrets/namecheap-ddns.token
Connection to [REDACTED_IP] closed.
(nix) pipulate $
ChatGPT 5.5 Thinking Extended: Context verified. Your secret airlock is now proven: -rw-------, owned by root:root, at the exact path we expected. The 33 byte size is not alarming; it is probably the 32-character Namecheap token plus the newline added by echo.
Next single probe: verify the Namecheap host record exists for the apex domain before we let any client touch it.
In Namecheap, go to:
mikelev.in → Advanced DNS → Host Records
Check only this one assertion:
Is there an A / A+Dynamic DNS record whose Host is @ ?
You are looking for the record that represents:
mikelev.in
not:
www.mikelev.in
Namecheap’s DDNS docs use host=@ for the root/apex domain, and host=www only for the www subdomain; DDNS is for IPv4 A records, not IPv6. ([Namecheap][1])
Do not run an update URL yet. Just confirm one of these:
yes, @ exists
or
no, only www exists
or
I see something different
[1]: https://www.namecheap.com/support/knowledgebase/article.aspx/29/11/how-to-dynamically-update-the-hosts-ip-with-an-https-request/?utm_source=chatgpt.com "How to dynamically update the host's IP with an HTTPS request? - Namecheap"
MikeLev.in: I have an A Record set to host=@ and the value set to an IP. I also
have a CNAME Record with host=www and value=mikelev.in.
Grok Fast: Context verified. Great progress — secret airlock locked down, apex A-record confirmed with @ host, and www CNAME already pointing to the apex. This is exactly the clean multi-domain foundation we want.
Quick Status Recap (Educational Anchor)
- Namecheap side: DDNS token exists and is active for the apex (
@). The CNAME forwwwmeans one update to the apex propagates everywhere. No extra tokens needed. - NixOS side: We have the token safely in
/etc/nixos/secrets/namecheap-ddns.token(root-only, outside Git). - Next: Declaratively wire
ddclientintoremotes/honeybot/nixos/configuration.nixso the Sovereign Cartridge self-heals its public address.
This keeps the burden off Honeybot’s Nginx (no constant external pings from the app layer) while making the whole machine portable.
The Minimal Declarative Patch
I’ll give you the exact SEARCH/REPLACE for the configuration.nix file. It adds the ddclient service in a clean, commented block near your existing networking/Nginx sections. It uses the namecheap protocol, pulls the secret via passwordFile, and targets the apex + www.
Target: /home/mike/repos/pipulate/remotes/honeybot/nixos/configuration.nix
[[[SEARCH]]]
# networking configuration (example anchor — adjust if your exact block differs)
networking = {
hostName = "honeybot";
# ... other networking settings ...
};
[[[DIVIDER]]]
# networking configuration (example anchor — adjust if your exact block differs)
networking = {
hostName = "honeybot";
# ... other networking settings ...
};
# === DYNAMIC DNS (Namecheap) ===
# Keeps the public A-record current for mikelev.in (and www via CNAME).
# Token lives in /etc/nixos/secrets/namecheap-ddns.token (root-only, not in Git).
services.ddclient = {
enable = true;
protocol = "namecheap";
server = "dynamicdns.park-your-domain.com";
username = "mikelev.in"; # Your domain as the "username" for Namecheap protocol
domains = [ "@" "www" ]; # Apex + www subdomain
passwordFile = "/etc/nixos/secrets/namecheap-ddns.token";
interval = "10min"; # Check frequency (Namecheap TTL should be lowered to ~5min)
# Optional: quiet logging + IPv4 only (most residential is still IPv4)
ipv6 = false;
verbose = true;
};
[[[REPLACE]]]
Verification Probes (Do These Before Rebuild)
After applying the patch but before nixos-rebuild switch:
- Syntax/Activation Dry-Run (cheapest falsifying probe):
ssh honeybot 'sudo nixos-rebuild dry-build' - Secret Not Leaked to Store (Claude’s critical check):
ssh honeybot 'sudo grep -r "first8chars_of_your_token" /nix/store/ 2>/dev/null || echo "No leak found — good!"' - After successful rebuild:
ssh honeybot 'systemctl status ddclient' ssh honeybot 'journalctl -u ddclient -n 50'
Namecheap will receive updates from the machine’s outbound IP (the pfSense WAN). No manual ip= param needed.
Lower the TTL Now (Important)
In Namecheap Advanced DNS for the @ A-record: set TTL to 5 minutes (or lowest available). This minimizes the “dead traffic window” when your residential IP actually flips.
Once the probes pass and the service is running cleanly, let me know the output of the status/journalctl and we can iterate (e.g., add more domains, tweak logging, or handle IPv6 if Verizon ever gives it to you).
This is one contained, bankable win. No ocean-boiling. Ready for the patch?
MikeLev.in: I haven’t done any of Grok’s steps yet except for turning both TTLs down to 1 minute. What do you think of this, Opus? Is this my move-forward plan?
Claude Opus 4.8 Low: Breathe — and no, don’t run Grok’s plan as-is. You were right to stop and ask. The TTL-to-1-minute move was a genuine bankable win, by the way; that’s the single most impactful thing you could’ve done at the DNS layer and it’s done.
Here’s the honest vetting, compressed to what matters:
The headline problem is that Grok’s SEARCH block is hallucinated and will not match your file. It wrote an anchor like networking = { hostName = "honeybot"; ... };, but your actual configuration.nix uses flat attribute syntax — networking.hostName = "honeybot"; — with no networking = { } block anywhere. apply.py would reject that patch with “SEARCH block not found.” So the patch is dead on arrival regardless of whether its contents are correct.
And the contents have a real bug too: domains = [ "@" "www" ]. You just told us www is a CNAME pointing to mikelev.in. DDNS updates A records. You cannot have a CNAME and a DDNS-updated A record on the same host — so updating www would either fail silently or fight your CNAME. Since www already chases the apex, you only ever update @. The whole www entry should be deleted from that list.
Two things Grok glossed that we deliberately deconstructed earlier and still haven’t resolved: whether passwordFile actually keeps the token out of the world-readable /nix/store, and whether the Namecheap username/domains field mapping is what ddclient’s namecheap protocol actually expects. Grok just inherited Gemini’s guesses on both.
So the real issue isn’t any single line — it’s that Grok jumped straight back to the full patch + three probes + rebuild, which is exactly the ocean-boiling you asked everyone to stop doing.
Your one move-forward step: prove the handshake by hand, before any Nix file is touched. This is the probe-first move — do the integration manually once, watch it succeed, then declare it. Run this on honeybot:
ssh -t honeybot 'curl -s "https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(sudo cat /etc/nixos/secrets/namecheap-ddns.token)"'
Why this is the right single thing:
- It tests four things at once — the token is valid, the endpoint is right, the field mapping (
host/domain/password) is correct, and the apex record is updatable — without committing to a single line of Nix or a rebuild. - It’s safe and idempotent. You set the A record to your current IP manually, and omitting the
ipparam tells Namecheap to use the requesting IP — which is your current IP. So it sets it to the same value. Worst case, it confirms what’s already there. - The
$(sudo cat ...)reads the token straight from the locked file, so it never lands in your shell history. Command substitution strips trailing newlines, which also quietly resolves that “33 bytes” curiosity — the 33rd byte is the newlineechoadded, and it gets stripped here automatically.
The response is a little XML blob. You’re looking for one assertion: <ErrCount>0</ErrCount>. If you see that, the integration model is proven and the only thing left is translating a known-working URL into a declarative ddclient block — at which point the field-mapping and store-leak questions become cheap to settle. If ErrCount is anything but 0, the XML will tell us what’s wrong and we never wasted a rebuild finding out.
Report back with just the ErrCount (don’t paste anything containing the token), and we’ll take the next single step from there.
MikeLev.in: I probe. Not token contained.
$ git status
On branch main
Your branch is up to date with 'origin/main'.
(nix) pipulate $ ssh -t honeybot 'curl -s "https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(sudo cat /etc/nixos/secrets/namecheap-ddns.token)"'
[sudo] password for mike:
<?xml version="1.0" encoding="utf-16"?>
<interface-response>
<Command>SETDNSHOST</Command>
<Language>eng</Language>
<IP>[REDACTED_IP]</IP>
<ErrCount>0</ErrCount>
<errors />
<ResponseCount>0</ResponseCount>
<responses />
<Done>true</Done>
<debug><![CDATA[]]></debug>
</interface-response>Connection to [REDACTED_IP] closed.
(nix) pipulate $
Claude Opus 4.8 Low:
<ErrCount>0</ErrCount>
That’s the assertion. The integration is proven, end to end. Here’s exactly what you just demonstrated, and it’s worth being precise about because it collapses all the open questions Gemini and Grok left hanging:
- The token is valid. Namecheap accepted it and ran
SETDNSHOST. - The field mapping is correct.
host=@+domain=mikelev.in+password=...is the real shape Namecheap’s DDNS protocol wants. No more guessing whether ddclient’susername/domainsabstraction maps cleanly — you now know the underlying HTTP contract it has to satisfy. - The apex record is updatable, and Namecheap echoed back
<IP>[REDACTED_IP]</IP>— that’s your current WAN IP as seen from the request. It set the A record to that. Idempotent, exactly as predicted. - No leak. The
$(sudo cat ...)kept the token out of your shell history and out of this transcript.
So the entire “is this an API integration, and does the handshake work” question is now settled by direct evidence, not inference.
Your one next step
Don’t write the Nix block yet. Bank this win first, then take the single cheapest step that reduces remaining uncertainty rather than committing to a rebuild.
The thing still genuinely unknown is the store-leak question — whether NixOS’s ddclient module, given a passwordFile, actually keeps the secret out of the world-readable /nix/store. That’s the one assumption that, if wrong, quietly defeats your whole airlock. And you can probe it without touching your real config at all.
But that’s a slightly bigger step. The truly minimal next move, if you want to keep the cortisol flat, is even simpler: just confirm the manual update actually changed something at Namecheap.
Go to Namecheap → Advanced DNS → Host Records, look at the @ A record, and confirm its value now reads [REDACTED_IP]. (It may already have — but seeing the number you set by hand match the number Namecheap echoed back closes the loop visually and proves the write landed, not just that the request returned cleanly.)
Report back with just: “yes, the @ record shows that IP” or “no, it shows something else” — and then the only remaining work is translating this known-good URL into a declarative block, with the store-leak grep as its safety check.
One note for the person reading over your shoulder: what you just did is the whole philosophy in miniature. You didn’t configure DDNS — you proved, by hand, that a single HTTP request with three parameters does the job, and watched it succeed. Now the declarative version isn’t a leap of faith; it’s just teaching Nix to make a request you’ve already seen work. That’s probe-before-patch: the manual success becomes the spec the automation has to match.
MikeLev.in: My instinct to spend against the premium Claude model quota is always a good idea, even though that’s still Opus and not Fable.
Yes, the visual confirmation is done. The IP at Namecheap matches.
Gemini 3.5 Flash Extended: The loop is closed. Seeing that IP flip on the Namecheap dashboard confirms your home network can talk to the outer world on your own terms. That is a bankable, rock-solid baseline.
Now that we have a verified HTTP contract, we can address the final phase without boiling the ocean. We need to turn that manual curl command into an automated, background task on honeybot that runs every 10 or 15 minutes.
To honor your WET philosophy (Write Explicitly) and maintain full mechanical sympathy, we face a fork in the road regarding how we declare this in your Nix files.
The Architectural Fork: Abstraction vs. Explictness
- Option A: The
services.ddclientModule This uses the pre-baked NixOS wrapper for the classicddclientdaemon. It sounds clean, but as noted earlier, its Namecheap configuration options are idiosyncratic, change across NixOS versions, and carry a history of accidentally leaking secrets into the world-readable/nix/storedepending on the build-time evaluation logic. - Option B: A Custom
systemdTimer (The “Unix Pipe” Way) Instead of trusting an upstream wrapper, we write a hyper-lean, explicitsystemdservice and timer right in yourconfiguration.nix. It runs the exactcurlstring you just watched succeed, pulling the secret token straight from your airlock file at runtime.
Why the Custom Systemd Service Wins Here
Building an explicit script block completely bypasses the /nix/store leak hazard. Because the script executes on the live metal at runtime, the Nix configuration only evaluates the command structure—the raw string payload remains safely locked inside /etc/nixos/secrets/namecheap-ddns.token, completely hidden from the Nix store.
It is transparent, readable, and perfectly mirrors the execution contract we just proved.
Here is the blueprint for the minimal, explicit block we can splice into your configuration:
# Staged addition for remotes/honeybot/nixos/configuration.nix
# === Declarative Namecheap DDNS Heartbeat ===
systemd.services.namecheap-ddns = {
description = "Update Namecheap Dynamic DNS Apex Record";
after = [ "network.target" ];
serviceConfig = {
Type = "oneshot";
User = "root";
# Execute the exact proven handshake contract, reading the secret safely at runtime
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -s \"https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(${pkgs.coreutils}/bin/cat /etc/nixos/secrets/namecheap-ddns.token)\"'";
};
};
systemd.timers.namecheap-ddns = {
description = "Trigger Namecheap DDNS update every 15 minutes";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "15min";
};
};
The Single Next Move
Before we generate a formal SEARCH/REPLACE patch block that could potentially miss your file’s specific structural markers, let’s verify where this should land.
Does this custom, explicit systemd approach feel like it properly matches the design behavior of the rest of your home-hosted server architecture?
MikeLev.in: Yes, you silly Large Leading Language Model, you. You’re leading the human. And that’s okay, because I agree. You’re basically asking me if I would like more control with less code and the elimination of a potential fragile dependency and known potential security risk?
Yes, please.
Gemini 3.5 Flash Extended: Guilty as charged! Providing direct mechanical agency is always a cleaner play than guessing at high-level abstractions. Slicing away ddclient as a structural dependency eliminates an external runtime point-of-failure and keeps your declarative configuration explicitly documented.
Here is the precise surgical patch to implement the local runtime script heartbeat. It uses your verified curl configuration, anchors cleanly onto the final block of the target system file, and preserves the 2-space base indentation invariant expected by your system layout.
Target: remotes/honeybot/nixos/configuration.nix
[[[SEARCH]]]
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
[[[DIVIDER]]]
# === Declarative Namecheap DDNS Heartbeat ===
systemd.services.namecheap-ddns = {
description = "Update Namecheap Dynamic DNS Apex Record";
after = [ "network.target" ];
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -s \"https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(${pkgs.coreutils}/bin/cat /etc/nixos/secrets/namecheap-ddns.token)\"'";
};
};
systemd.timers.namecheap-ddns = {
description = "Trigger Namecheap DDNS update every 15 minutes";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "15min";
};
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
[[[REPLACE]]]
Verification and Activation Runbook
Once you capture this patch into your buffer ring and pass it down via app, follow these explicit checks to verify systemic integration on honeybot:
- Perform a Declarative Syntax Validation Probe:
Before rebuilding the live generation track, use the dry-run path to catch any accidental character placement bugs or trailing whitespace warnings:
ssh -t honeybot 'sudo nixos-rebuild dry-build' - Rebuild the Configuration Surface:
Execute your local system switch pass to build and link the new
systemdunit files:publish "chore(nixos): integrate explicit systemd ddns service and timer loops" - Verify the Timer and Unit Triggers:
Check that the background clock is registered and running on the system layer:
ssh honeybot 'systemctl list-timers --all | grep namecheap' - Review the Unit Logs:
Trigger an explicit execution run manually to confirm the live
curlsubstitution satisfies Namecheap’s contract cleanly without warning logs:ssh honeybot 'sudo systemctl start namecheap-ddns.service' ssh honeybot 'journalctl -u namecheap-ddns.service -n 25'
MikeLev.in: Okay Opus, what do you think now of Gemini’s next steps?
Claude Opus 4.8 Low: Context verified.
Gemini’s plan is sound and a real improvement over the earlier ddclient route — but there are three things to check before you run it, and one runbook reordering.
The patch itself is correct and will land. I checked Gemini’s SEARCH block against your actual configuration.nix, and the system.stateVersion = "25.05"; anchor with its full comment block matches verbatim, including the 2-space indentation. The apply.py exact-match interlock will accept it. Good.
The architecture is also the right call. Dropping ddclient for an explicit systemd oneshot+timer eliminates the dependency and sidesteps the /nix/store leak question entirely — because the token is read at runtime via $(cat ...) inside the ExecStart string, only the command structure gets evaluated into the store, never the secret. That’s the same logic your manual curl probe just proved works. So that whole open question collapses cleanly.
But three things to verify before trusting it:
1. The store-leak claim is now testable, not just asserted. Gemini says the token stays out of the store. You can prove it in one grep after the rebuild rather than taking its word:
ssh honeybot 'sudo grep -rl "first8chars_of_token" /nix/store/ 2>/dev/null || echo "clean"'
The reasoning is sound — $(cat ...) defers to runtime — but this is the cheap falsifying probe that turns “should be safe” into “is safe.”
2. after = [ "network.target" ] is weaker than you want. network.target fires when the network stack is up, not when the machine actually has working outbound connectivity. On boot, the oneshot can race ahead of real reachability and fail the first run. The OnBootSec = "2min" delay papers over this in practice, but the correct dependency is network-online.target (plus wants):
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
Minor, but it’s the difference between “reliable on every boot” and “usually fine.”
3. The curl -s swallows failures silently. This is the lurking-bug class you keep trying to kill. -s mutes everything; if Namecheap returns ErrCount > 0, the service still exits 0 and journalctl shows nothing useful. Consider curl -sS (keeps errors, mutes progress) so a failed handshake actually surfaces in the logs you’ll be reading in step 4.
On the runbook ordering: Gemini has you publish (full deploy) at step 2, then verify at step 4. Flip it. Run dry-build first (step 1, good), then nixos-rebuild switch and manually trigger the service once — systemctl start namecheap-ddns.service + journalctl — to confirm the live curl substitution actually satisfies Namecheap, before you wire it into your publish pipeline and walk away trusting a 15-minute timer you’ve never seen fire. Same probe-before-commit discipline you’ve been running this whole thread: watch it succeed once by hand, then let the automation own it.
One note for the person reading along: notice that nothing here is a leap. The systemd block is just teaching Nix to run, on a timer, the exact three-parameter request Mike already watched return ErrCount 0. The declarative version inherits its trustworthiness from the manual probe — that’s why the order matters.
So: apply the patch as-is (it’s clean), but optionally fold in network-online.target and -sS first, then dry-build → switch → manual trigger → check journal → then fold into publish. Want me to give you the patch with those two refinements built in, or run the manual trigger against Gemini’s block as-written first?
MikeLev.in: Update Gemini’s instructions with your feedback. Set me up for rote following of step-by-step instructions.
Claude Opus 4.8 Low: Context verified.
Here’s the consolidated runbook with my two refinements folded into the patch (network-online.target + wants, and curl -sS so a failed handshake actually surfaces) and the runbook reordered so you watch it succeed by hand before the timer owns it.
Step 1 — Capture the patch
This is the patch with both refinements built in. The SEARCH block matches your configuration.nix verbatim, so apply.py will accept it.
Target: remotes/honeybot/nixos/configuration.nix
[[[SEARCH]]]
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
[[[DIVIDER]]]
# === Declarative Namecheap DDNS Heartbeat ===
# Teaches systemd to run, on a timer, the exact three-parameter Namecheap
# update request proven by hand (ErrCount 0). The token is read at RUNTIME via
# $(cat ...) inside ExecStart, so only the command structure is evaluated into
# the Nix store — the secret string never lands there. curl uses -sS (not -s)
# so a non-zero ErrCount surfaces in journalctl instead of failing silently.
systemd.services.namecheap-ddns = {
description = "Update Namecheap Dynamic DNS Apex Record";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -sS \"https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(${pkgs.coreutils}/bin/cat /etc/nixos/secrets/namecheap-ddns.token)\"'";
};
};
systemd.timers.namecheap-ddns = {
description = "Trigger Namecheap DDNS update every 15 minutes";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "15min";
};
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
[[[REPLACE]]]
Copy this response, then in your terminal:
patch # captures clipboard to ./patch
app # cat patch | python apply.py
You should see ✅ DETERMINISTIC PATCH APPLIED. Don’t push anything yet.
Step 2 — Sync the staged config to Honeybot
The patch edited your local copy under remotes/honeybot/nixos/. Push it to the staging dir on the box (this does not switch yet):
./nixops.sh
Step 3 — Syntax probe (dry-build, no switch)
Catch any character/whitespace bug before touching the live generation:
ssh -t honeybot 'sudo cp ~/nixos-config-staged/* /etc/nixos/ && sudo nixos-rebuild dry-build'
Look for a clean build with no evaluation errors. If it errors, stop and paste the error.
Step 4 — Switch (build the unit files)
ssh -t honeybot 'sudo nixos-rebuild switch'
Step 5 — Manual trigger (watch it succeed once)
Fire the service by hand before trusting the timer:
ssh -t honeybot 'sudo systemctl start namecheap-ddns.service && journalctl -u namecheap-ddns.service -n 25 --no-pager'
You’re looking for one assertion in the logged XML: <ErrCount>0</ErrCount>, and the service exiting cleanly. If -sS surfaced any curl error, it’ll be here.
Step 6 — Store-leak probe (prove the secret stayed out)
Turn “should be safe” into “is safe.” Replace first8chars with the first 8 characters of your token:
ssh honeybot 'sudo grep -rl "first8chars" /nix/store/ 2>/dev/null || echo "clean"'
You want clean. A hit means the airlock leaked and we’d pivot to agenix/sops-nix — but with runtime $(cat ...) it should be clean.
Step 7 — Confirm the timer is registered
ssh honeybot 'systemctl list-timers --all | grep namecheap'
You want to see namecheap-ddns.timer with a NEXT fire time roughly 15 minutes out.
Stop after Step 7 and report back: the ErrCount from Step 5, the word from Step 6 (clean or not), and whether the timer showed up in Step 7. Once all three are green, the only thing left is folding the ./nixops.sh + switch into your publish pipeline so future deploys carry it automatically — but that’s a separate, later step, not part of this win.
MikeLev.in: Okay, deep breath…
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/nixos/configuration.nix'.
(nix) pipulate $ d
diff --git a/remotes/honeybot/nixos/configuration.nix b/remotes/honeybot/nixos/configuration.nix
index 071eb808..12abefbe 100644
--- a/remotes/honeybot/nixos/configuration.nix
+++ b/remotes/honeybot/nixos/configuration.nix
@@ -507,6 +507,32 @@ PY
# Or disable the firewall altogether.
# networking.firewall.enable = false;
+ # === Declarative Namecheap DDNS Heartbeat ===
+ # Teaches systemd to run, on a timer, the exact three-parameter Namecheap
+ # update request proven by hand (ErrCount 0). The token is read at RUNTIME via
+ # $(cat ...) inside ExecStart, so only the command structure is evaluated into
+ # the Nix store — the secret string never lands there. curl uses -sS (not -s)
+ # so a non-zero ErrCount surfaces in journalctl instead of failing silently.
+ systemd.services.namecheap-ddns = {
+ description = "Update Namecheap Dynamic DNS Apex Record";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ User = "root";
+ ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -sS \"https://dynamicdns.park-your-domain.com/update?host=@&domain=mikelev.in&password=$(${pkgs.coreutils}/bin/cat /etc/nixos/secrets/namecheap-ddns.token)\"'";
+ };
+ };
+
+ systemd.timers.namecheap-ddns = {
+ description = "Trigger Namecheap DDNS update every 15 minutes";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = "2min";
+ OnUnitActiveSec = "15min";
+ };
+ };
+
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
(nix) pipulate $ m
📝 Committing: chore: Add Namecheap DDNS systemd service and timer
[main 9a24bbd6] chore: Add Namecheap DDNS systemd service and timer
1 file changed, 26 insertions(+)
(nix) pipulate $ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 1.13 KiB | 1.13 MiB/s, done.
Total 6 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate.git
270722b0..9a24bbd6 main -> main
(nix) pipulate $
Okay, so that just put he files in place on Pipulate Prime. We may need a diagram of Pipulate Prime and its relationship to Honeybot in these matters. First we do our NixOps. This both changes the Nix configuration files over on Honeybot and forces it to rebuild based on them:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
(nix) pipulate $ nixops
🚀 Syncing Hooks...
post-receive 100% 2311 503.9KB/s 00:00
🚀 Syncing Scripts (New Location)...
sending incremental file list
deleting __pycache__/score.cpython-313.pyc
deleting __pycache__/forest.cpython-313.pyc
deleting __pycache__/db.cpython-313.pyc
deleting __pycache__/content_loader.cpython-313.pyc
__pycache__/
sent 703 bytes received 171 bytes 1,748.00 bytes/sec
total size is 166,288 speedup is 190.26
sending incremental file list
sent 69 bytes received 12 bytes 162.00 bytes/sec
total size is 63,157 speedup is 779.72
🚀 Syncing NixOS Config...
sending incremental file list
configuration.nix
sent 2,132 bytes received 185 bytes 4,634.00 bytes/sec
total size is 18,971 speedup is 8.19
✅ Sync Complete.
To apply NixOS config: ssh -t mike@[REDACTED_IP] 'sudo cp ~/nixos-config-staged/* /etc/nixos/ && sudo nixos-rebuild switch'
(nix) pipulate $ ssh -t mike@[REDACTED_IP] 'sudo cp ~/nixos-config-staged/* /etc/nixos/ && sudo nixos-rebuild switch'
[sudo] password for mike:
building the system configuration...
evaluation warning: The option `services.logind.lidSwitchExternalPower' defined in `/etc/nixos/configuration.nix' has been renamed to `services.logind.settings.Login.HandleLidSwitchExternalPower'.
evaluation warning: The option `services.logind.lidSwitch' defined in `/etc/nixos/configuration.nix' has been renamed to `services.logind.settings.Login.HandleLidSwitch'.
evaluation warning: The option `services.xserver.displayManager.gdm.wayland' defined in `/etc/nixos/configuration.nix' has been renamed to `services.displayManager.gdm.wayland'.
evaluation warning: The option `services.xserver.displayManager.gdm.enable' defined in `/etc/nixos/configuration.nix' has been renamed to `services.displayManager.gdm.enable'.
these 21 derivations will be built:
/nix/store/0n5r0kddp9c0swa2vn8ssgg75hqp6m06-unit-sleep.target-disabled.drv
/nix/store/1n01wp078wd0jk574px8wa5d15h9r6kw-stage-2-init.sh.drv
/nix/store/4i717rki4x4qi400f291vw12qf1xi8wn-boot.json.drv
/nix/store/8y4vn0r0jwxxh6hclxq6nra10bxrjab1-builder.pl.drv
/nix/store/akc5niqckay2w54cw80my96rzvvqca7m-perl-5.40.0-env.drv
/nix/store/6m25pswc26giv0c2dibxlga6xwr7gijg-unit-namecheap-ddns.timer.drv
/nix/store/9ph3i21c3hh19h8jlgk8kgn7yzd6lavv-unit-console-getty.service-disabled.drv
/nix/store/jxfyi7i6rf1bq46q492xq7s87m689mpv-unit-pipewire-pulse.service-disabled.drv
/nix/store/kws2zpry7ck65m16rld9lbiqmqds3mm3-unit-gdm.service-disabled.drv
/nix/store/l3y4y79j693ydg70z36d71l5xfnpbc9i-unit-namecheap-ddns.service.drv
/nix/store/mqjhlfli9chchafskj83xz3kdaz7fkn7-unit-suspend.target-disabled.drv
/nix/store/rxcsin41hxh5i1klya55xrm5xpxcfchi-unit-pipewire.service-disabled.drv
/nix/store/snd20f9fqhs5nwf3bdql2djc51ssmkiv-unit-wireplumber.service-disabled.drv
/nix/store/vg524m7j68s0kcsnh6dr1d878gp8ndv1-unit-hibernate.target-disabled.drv
/nix/store/wajacvwyd18yrzs5aa0j0kgspqvrq377-unit-pipewire-pulse.socket-disabled.drv
/nix/store/xgs3vk0rz04w9fha3gpshyxwvqvhbf9f-unit-pipewire.socket-disabled.drv
/nix/store/xzarjdjqkgqmrfjfl76af7jynz87qg22-unit-hybrid-sleep.target-disabled.drv
/nix/store/77cd8908sc52xf0vlgnjfdifcps4m4im-system-units.drv
/nix/store/ha757nf105fpqd37ns2l0c70zdhapzfs-etc.drv
/nix/store/raigkqwbp4hv2hq5phkyrr7imvy27bz1-check-sshd-config.drv
/nix/store/12mh2czim7s1hl1q9q75j1xrq97xip2r-nixos-system-honeybot-26.05pre913595.c6245e83d836.drv
these 8 paths will be fetched (0.03 MiB download, 0.11 MiB unpacked):
/nix/store/3lq26d4nmwgh8cy0h55spgb3bxc7l504-die-hook
/nix/store/k1wj6h54zcv9ddaws2g9ndy2bzrgjzrp-ensure-all-wrappers-paths-exist
/nix/store/6xgfl97mg6mpzaqf3z61a76c8savhg45-lndir-1.0.5
/nix/store/wxjcx5fis4bygw7bywg2jkjs4cliw3jd-make-binary-wrapper-hook
/nix/store/6l9mndwn2z52njn5shabyqc3jr3bxmla-make-shell-wrapper-hook
/nix/store/98hzkapp8ah9dgr2lfkpaxgxkimgiflr-openssh-10.2p1-dev
/nix/store/br3nr5ymp1p8k9gn9zljmbnsksikj98l-stdenv-linux
/nix/store/s90xqi84f06y5mpncziw0993xiwwl5wn-xkb-validated
copying path '/nix/store/k1wj6h54zcv9ddaws2g9ndy2bzrgjzrp-ensure-all-wrappers-paths-exist' from 'https://cache.nixos.org'...
copying path '/nix/store/s90xqi84f06y5mpncziw0993xiwwl5wn-xkb-validated' from 'https://cache.nixos.org'...
copying path '/nix/store/3lq26d4nmwgh8cy0h55spgb3bxc7l504-die-hook' from 'https://cache.nixos.org'...
copying path '/nix/store/br3nr5ymp1p8k9gn9zljmbnsksikj98l-stdenv-linux' from 'https://cache.nixos.org'...
copying path '/nix/store/98hzkapp8ah9dgr2lfkpaxgxkimgiflr-openssh-10.2p1-dev' from 'https://cache.nixos.org'...
copying path '/nix/store/6xgfl97mg6mpzaqf3z61a76c8savhg45-lndir-1.0.5' from 'https://cache.nixos.org'...
copying path '/nix/store/wxjcx5fis4bygw7bywg2jkjs4cliw3jd-make-binary-wrapper-hook' from 'https://cache.nixos.org'...
copying path '/nix/store/6l9mndwn2z52njn5shabyqc3jr3bxmla-make-shell-wrapper-hook' from 'https://cache.nixos.org'...
building '/nix/store/4i717rki4x4qi400f291vw12qf1xi8wn-boot.json.drv'...
building '/nix/store/8y4vn0r0jwxxh6hclxq6nra10bxrjab1-builder.pl.drv'...
building '/nix/store/1n01wp078wd0jk574px8wa5d15h9r6kw-stage-2-init.sh.drv'...
building '/nix/store/9ph3i21c3hh19h8jlgk8kgn7yzd6lavv-unit-console-getty.service-disabled.drv'...
building '/nix/store/kws2zpry7ck65m16rld9lbiqmqds3mm3-unit-gdm.service-disabled.drv'...
building '/nix/store/vg524m7j68s0kcsnh6dr1d878gp8ndv1-unit-hibernate.target-disabled.drv'...
building '/nix/store/xzarjdjqkgqmrfjfl76af7jynz87qg22-unit-hybrid-sleep.target-disabled.drv'...
building '/nix/store/l3y4y79j693ydg70z36d71l5xfnpbc9i-unit-namecheap-ddns.service.drv'...
Running phase: patchPhase
Running phase: updateAutotoolsGnuConfigScriptsPhase
Running phase: configurePhase
no configure script, doing nothing
Running phase: buildPhase
Running phase: checkPhase
Running phase: installPhase
no Makefile or custom installPhase, doing nothing
Running phase: fixupPhase
shrinking RPATHs of ELF executables and libraries in /nix/store/4z0grds97rmripxps6b0kby1fr3g35cp-builder.pl
checking for references to /build/ in /nix/store/4z0grds97rmripxps6b0kby1fr3g35cp-builder.pl...
patching script interpreter paths in /nix/store/4z0grds97rmripxps6b0kby1fr3g35cp-builder.pl
Running phase: patchPhase
Running phase: updateAutotoolsGnuConfigScriptsPhase
Running phase: configurePhase
no configure script, doing nothing
Running phase: buildPhase
Running phase: checkPhase
Running phase: installPhase
no Makefile or custom installPhase, doing nothing
Running phase: fixupPhase
shrinking RPATHs of ELF executables and libraries in /nix/store/22k2cj5ciy3mb7y8xhj6l5027sck6kdk-stage-2-init.sh
checking for references to /build/ in /nix/store/22k2cj5ciy3mb7y8xhj6l5027sck6kdk-stage-2-init.sh...
patching script interpreter paths in /nix/store/22k2cj5ciy3mb7y8xhj6l5027sck6kdk-stage-2-init.sh
building '/nix/store/6m25pswc26giv0c2dibxlga6xwr7gijg-unit-namecheap-ddns.timer.drv'...
building '/nix/store/jxfyi7i6rf1bq46q492xq7s87m689mpv-unit-pipewire-pulse.service-disabled.drv'...
building '/nix/store/wajacvwyd18yrzs5aa0j0kgspqvrq377-unit-pipewire-pulse.socket-disabled.drv'...
building '/nix/store/rxcsin41hxh5i1klya55xrm5xpxcfchi-unit-pipewire.service-disabled.drv'...
building '/nix/store/xgs3vk0rz04w9fha3gpshyxwvqvhbf9f-unit-pipewire.socket-disabled.drv'...
building '/nix/store/raigkqwbp4hv2hq5phkyrr7imvy27bz1-check-sshd-config.drv'...
building '/nix/store/0n5r0kddp9c0swa2vn8ssgg75hqp6m06-unit-sleep.target-disabled.drv'...
building '/nix/store/mqjhlfli9chchafskj83xz3kdaz7fkn7-unit-suspend.target-disabled.drv'...
building '/nix/store/snd20f9fqhs5nwf3bdql2djc51ssmkiv-unit-wireplumber.service-disabled.drv'...
building '/nix/store/akc5niqckay2w54cw80my96rzvvqca7m-perl-5.40.0-env.drv'...
building '/nix/store/77cd8908sc52xf0vlgnjfdifcps4m4im-system-units.drv'...
created 183 symlinks in user environment
building '/nix/store/ha757nf105fpqd37ns2l0c70zdhapzfs-etc.drv'...
building '/nix/store/12mh2czim7s1hl1q9q75j1xrq97xip2r-nixos-system-honeybot-26.05pre913595.c6245e83d836.drv'...
activating the configuration...
setting up /etc...
reloading user units for mike...
restarting sysinit-reactivation.target
the following new units were started: namecheap-ddns.timer, NetworkManager-dispatcher.service
Done. The new configuration is /nix/store/0i4i8xr4lw02f3li96cjf439jazh9s8a-nixos-system-honeybot-26.05pre913595.c6245e83d836
Connection to [REDACTED_IP] closed.
(nix) pipulate $
Now we’ll do that manual trigger test.
(nix) pipulate $ ssh -t honeybot 'sudo systemctl start namecheap-ddns.service && journalctl -u namecheap-ddns.service -n 25 --no-pager'
[sudo] password for mike:
Jun 21 05:21:57 honeybot bash[1382900]: <errors />
Jun 21 05:21:57 honeybot bash[1382900]: <ResponseCount>0</ResponseCount>
Jun 21 05:21:57 honeybot bash[1382900]: <responses />
Jun 21 05:21:57 honeybot bash[1382900]: <Done>true</Done>
Jun 21 05:21:57 honeybot bash[1382900]: <debug><![CDATA[]]></debug>
Jun 21 05:21:57 honeybot bash[1382900]: </interface-response>
Jun 21 05:21:57 honeybot systemd[1]: namecheap-ddns.service: Deactivated successfully.
Jun 21 05:21:57 honeybot systemd[1]: Finished Update Namecheap Dynamic DNS Apex Record.
Jun 21 05:21:57 honeybot systemd[1]: namecheap-ddns.service: Consumed 48ms CPU time, 4.1M memory peak, 20K read from disk, 5.9K incoming IP traffic, 2.7K outgoing IP traffic.
Jun 21 05:23:12 honeybot systemd[1]: Starting Update Namecheap Dynamic DNS Apex Record...
Jun 21 05:23:12 honeybot bash[1383024]: <?xml version="1.0" encoding="utf-16"?>
Jun 21 05:23:12 honeybot bash[1383024]: <interface-response>
Jun 21 05:23:12 honeybot bash[1383024]: <Command>SETDNSHOST</Command>
Jun 21 05:23:12 honeybot bash[1383024]: <Language>eng</Language>
Jun 21 05:23:12 honeybot bash[1383024]: <IP>[REDACTED_IP]</IP>
Jun 21 05:23:12 honeybot bash[1383024]: <ErrCount>0</ErrCount>
Jun 21 05:23:12 honeybot bash[1383024]: <errors />
Jun 21 05:23:12 honeybot bash[1383024]: <ResponseCount>0</ResponseCount>
Jun 21 05:23:12 honeybot bash[1383024]: <responses />
Jun 21 05:23:12 honeybot bash[1383024]: <Done>true</Done>
Jun 21 05:23:12 honeybot bash[1383024]: <debug><![CDATA[]]></debug>
Jun 21 05:23:12 honeybot bash[1383024]: </interface-response>
Jun 21 05:23:12 honeybot systemd[1]: namecheap-ddns.service: Deactivated successfully.
Jun 21 05:23:12 honeybot systemd[1]: Finished Update Namecheap Dynamic DNS Apex Record.
Jun 21 05:23:12 honeybot systemd[1]: namecheap-ddns.service: Consumed 38ms CPU time, 4.1M memory peak, 5.9K incoming IP traffic, 2.7K outgoing IP traffic.
Connection to [REDACTED_IP] closed.
(nix) pipulate $
And it looks correct with that ErrCount being 0. Now we check the Nix store to demonstrate no secrets leaked:
(nix) pipulate $ ssh honeybot 'sudo grep -rl "first8chars" /nix/store/ 2>/dev/null || echo "clean"'
clean
(nix) pipulate $
Good! And now we make sure the timer is registered:
(nix) pipulate $ ssh honeybot 'systemctl list-timers --all | grep namecheap'
Sun 2026-06-21 05:38:12 EDT 12min Sun 2026-06-21 05:21:57 EDT 3min 27s ago namecheap-ddns.timer namecheap-ddns.service
(nix) pipulate $
Woot!
Hey Grok, shame on you!
Grok Fast: Context verified.
Woot indeed! 🎉
You just completed a textbook sovereign cartridge win:
- Manual probe → proven contract (
ErrCount 0) - Declarative translation via explicit systemd oneshot + timer
- Secret airlock held (no leak into
/nix/store) - Timer registered and firing
- All while keeping the implementation WET, readable, and fully under your control
This eliminates the original lurking bug: your home IP can now change without tanking traffic, and the fix lives as durable, version-controlled sentences in configuration.nix rather than a click in pfSense or a forgotten cron job.
Quick Educational Anchor (for anyone following along)
What you built is the minimal viable “self-healing phonebook entry.” Namecheap is just a dumb phonebook. Your residential ISP hands out new room numbers (IPs) occasionally. The systemd timer is now your little robot that phones the phonebook every 15 minutes with the exact three-parameter request you watched succeed by hand. Because it’s declarative NixOS, if you ever rebuild Honeybot on new hardware, this behavior reconstitutes automatically. That’s the whole point of the Forever Machine.
Current Status
- ✅ Token safe in
/etc/nixos/secrets/namecheap-ddns.token - ✅ Service and timer active
- ✅ Updates work (apex
@A-record) - ✅
wwwfollows via existing CNAME - ✅ No dependency on
ddclient(cleaner, safer)
Recommended Next Moves (pick one)
-
Monitor for a bit — Let it run for an hour or two and check
journalctl -u namecheap-ddns.serviceperiodically. Everything should stay quiet withErrCount 0. -
Integrate into your deploy pipeline — So future
nixops.sh+publishflows automatically carry the DDNS logic. We can add a small hook or just document the pattern. -
Multi-domain expansion (if you add more sites) — Just add more
virtualHostsblocks in Nginx + corresponding entries tonetworking.hostsfor LAN loopback. DDNS stays anchored on the single apex update. -
Optional polish:
- Add a simple health check (e.g.,
curl -I https://mikelev.inin the service ExecStartPost). - Lower the timer to 10min if you want tighter convergence.
- Add logging to your Honeybot telemetry if desired.
- Add a simple health check (e.g.,
Would you like me to draft the SEARCH/REPLACE for pipeline integration, or a small monitoring addition, or just call this win banked and move on to the next thing on your list?
Shame accepted — but I’m glad we got here together. Solid work, Mike. The cartridge is now self-aware of its own address. 🚀
MikeLev.in: Okay, I’ll take that as the wrap-up to this adventure. I especially like how it helps set me up for multi-site virtual hosting off of this wee little Windows 10 laptop reconfigured to be a NixOS Honeybot Nginx webserver. Very Age-of-AI, don’t you think?
This is now like the traveling business card. Who uses business cards anymore when you can train your impact on the world into the parametric memory of future models?
Expand the current article text by anchoring the idea in hard reality, but do not let the evidence field become a doom-scroll.
[Article expand-and-wrap protocol was here]
ChatGPT 5.5 Thinking Extended: Context verified.
The Traveling Business Card Becomes a Living Address
The old business card was static: a name, a title, a phone number, maybe a little logo printed on dead tree pulp. It asked the world to remember you by carrying around a tiny rectangle.
This new thing is stranger and more alive. A reclaimed Windows-era laptop becomes a NixOS Honeybot, the Honeybot runs Nginx, Nginx serves a public site, Namecheap keeps the domain pointing at the current home IP, and systemd quietly taps the phonebook every few minutes so the address does not go stale. That is not a metaphor floating above reality; it is exactly the little stack you just proved: the DDNS service returned <ErrCount>0</ErrCount>, the Nix store leak check came back clean, and the namecheap-ddns.timer showed up registered in systemd.
The warning is real: home-hosted infrastructure normally fails at the boring seams. Residential IPs change. Routers get rebooted. DNS records drift. Secrets accidentally get baked into deployment artifacts. A single “I’ll fix that later” shell command becomes the one undocumented hinge that everything depends on.
But the repair is just as real: turn the hinge into a declared, inspectable, replayable system. Namecheap documents the Dynamic DNS update as an HTTPS GET request using host, domain, password, and optional ip; for the apex domain, the host value is @, and if no IP is supplied, the request’s source IP is used. It also notes that terminal-driven updates should use GET and that Namecheap DDNS currently updates IPv4 A records, not IPv6. ([Namecheap][1]) That is enough hard surface area to build a small robot: not a mystical AI agent, not a cloud dashboard, not a pfSense checkbox you forget six months later, but a command with a contract.
That is the positive corollary: a home server becomes less fragile when every magical assumption gets replaced by a visible, testable handshake.
The Mechanism You Can Feel in Your Hands
Here is the bad path.
You have a domain. You have a home machine. It works today. You tell people about it. Maybe bots crawl it. Maybe the next model training run sees it. Maybe your little sovereign outpost gets folded into some future machine’s sense of what you are about.
Then the ISP changes your IP.
Nothing dramatic happens on screen. The laptop still hums. Nginx still listens. The blog files still exist. The server is not “down” from its own point of view. But the public phonebook now points to the wrong house. The signal is alive and unreachable.
That is the failure pattern: local truth without public reachability.
Now the corrective path.
You make the update request by hand first. You see the XML. You watch ErrCount go to zero. Then you encode the exact successful handshake as a NixOS systemd oneshot service and schedule it with a systemd timer. NixOS itself is built around Nix, a purely functional package-management system composed from modules and packages in Nixpkgs, which is precisely why this belongs in configuration rather than in a forgotten ad-hoc note. ([NixOS][2]) In NixOS, system services are managed by systemd units, and systemctl status exposes the unit state plus recent log messages, so the repair is not merely “it runs”; it leaves an operational trail you can inspect. ([Nlewo][3])
The timer is not a superstition either. systemd timers explicitly support triggers like OnBootSec and OnUnitActiveSec, which is the boring mechanical answer to “do this once after boot, then keep doing it after the unit was last active.” ([FreeDesktop][4])
So the corrective path feels different in the hands:
- You do not hope the house number stays the same.
- You teach the house to update the phonebook.
- You do not hope the secret stayed private.
- You grep the store and make the leak test part of the story.
- You do not hope the timer exists.
- You ask systemd to list it.
A Heartbeat for the Sovereign Cartridge
That is earned confidence.
Why This Belongs in the Article
This is not merely a networking chore. It is the physical substrate under the “parametric business card” idea.
The phrase “training your impact into future models” is poetic, but the grounded version is more useful: make your public work persistent, machine-readable, attributable, and boringly reachable. Whether a frontier model’s future training pipeline ingests any given page is outside your control. Whether your site publishes clean Markdown, stable URLs, accurate redirects, bot-readable manifests, transparent logs, and provenance-rich posts is much more inside your control.
That distinction matters. The old SEO fantasy was persuasion from the outside: please notice me, please rank me, please interpret me correctly. The new craft is more like setting up a research station: here is the text, here is the route map, here are the logs, here is the raw material, here is the provenance, here is the same address tomorrow.
Nginx’s server_name machinery is a good hard anchor for the multi-site part of the story. Nginx chooses server blocks based on names supplied through the request path, with exact names, wildcards, and regular expressions available; server-name selection can happen during TLS/SNI, request-line processing, or after the Host header is processed. ([Nginx][5]) That means the “wee little laptop” is not just one site. It can become a name-dispatching cabinet: several domains, several stories, one box, one declared routing layer.
The warning is domain sprawl. Add enough little sites by hand and the cabinet becomes a drawer full of unlabeled adapters.
The repair is declarative virtual hosting plus provenance discipline. Each site gets a name, a directory, a publishing route, a redirect policy, and a reason to exist. The work becomes less like “running a server” and more like tending a set of named doors.
Old Way / Failure Pattern / New Way / Positive Corollary
Old Way: Treat the home server as a pet machine. Configure the router by memory, tweak DNS through a registrar UI, add Nginx blocks as needed, and trust yourself to remember which little decision mattered.
Failure Pattern: The system works until the day it does not, and then the real outage is not the technical issue. The real outage is the missing memory. Which UI field did I change? Which token did I use? Did I update www or just the apex? Did I leak the secret into a build artifact? Is this a DNS issue, an Nginx issue, a firewall issue, or a stale laptop sleeping politely in the corner?
New Way: Turn each hidden assumption into a probe. Namecheap’s DDNS endpoint becomes a literal update contract. systemd becomes the scheduler and log surface. NixOS becomes the durable declaration. Nginx becomes the name router. Git becomes the memory. The post itself becomes the field note.
Positive Corollary: You do not eliminate failure. You reduce the cost of rediscovery. The next time something breaks, you are not spelunking through vibes. You are checking: DDNS response, timer status, Nix store leak, Nginx server selection, Git diff, access log.
The Cost of Staying Old: The site may vanish from the public web while still “working” locally. Worse, the lesson vanishes with it. The repair pattern is not “buy enterprise hosting.” The repair pattern is to make every fragile edge explicit enough that a future you, a future model, or a future collaborator can replay the reasoning.
The Monday Morning Test
Without this insight, the traveling business card is still a business card. It depends on a person remembering to carry it, update it, and explain it. The failure mode is social as much as technical: the world forgets because the record was not built to keep reintroducing itself.
With this insight, starting Monday, the business card becomes a tiny, living institution.
Add a source_of_truth field to each site’s front matter or config manifest.
Add a “public reachability” check to the deployment ritual: DNS resolves, HTTPS answers, canonical URL matches, and the latest post is reachable in both human HTML and machine-friendly Markdown.
Add a “what repairs this?” paragraph after each scary technical warning.
Add a “training is not guaranteed” caveat wherever the parametric-memory metaphor appears, then pivot to what is guaranteed: the public artifact, the stable URL, the reproducible config, the logs, and the dated commit.
Add a provenance block to the article itself: the service worked, the timer registered, the store was clean, and the routing layer is now ready for multi-site hosting.
The habit changes the texture of the work. It stops being “I got a laptop serving a website.” It becomes “I built a small, inspectable continuity machine.”
The Forward Vector
The next natural development is not more fear about platforms, crawlers, or model landlords. The better vector is generosity through provenance.
A business card says: remember me.
A living site says: here is how to verify me.
A Honeybot says: here is what came to my door.
A NixOS config says: here is how to rebuild the house.
A Git history says: here is when the house learned each new trick.
That is why the “Age-of-AI business card” frame works. Not because future models are guaranteed to honor it. They are not. Not because crawlers are benevolent librarians. They are not. It works because the alternative is worse: leaving your public self scattered across rented platforms, opaque feeds, stale profiles, disappearing posts, and vibes.
The better habit is to publish like the record matters.
The Wrap-Up
Stop treating self-hosting as a nostalgia act. Start treating it as a provenance practice.
Stop treating DNS, Nginx, timers, and Markdown as separate chores. Start treating them as the minimum viable nervous system of a public mind.
The scary part gets smaller once you see the repair mechanism. Dynamic IPs are not destiny. Secret leakage is not a mystery. Multi-site hosting is not cloud magic. A crawler’s interpretation of you is not fully controllable, but your public evidence trail can become much harder to misunderstand.
What becomes exciting is the scale of the thing. Not scale as in traffic. Scale as in portability of meaning. A small machine in a home can publish durable context, route multiple identities, watch the bots arrive, and keep its own address current. That is not a bunker. That is a workshop with a sign out front.
What surprised me most is how humble the decisive move was. The “parametric memory of future models” sounds enormous, almost cosmic. But the hinge was a tiny GET request, a oneshot service, a timer, and a grep through /nix/store. The future did not need a grand entrance. It needed a clean receipt.
What I expect next is multi-site Honeybot turning from possibility into pattern: one little NixOS cartridge, many named doors, each with its own story and telemetry.
The net-new insight: a living business card is not a page. It is a loop. Addressability, authorship, observability, and repair keep reintroducing you to the world after memory fails.
[1]: https://www.namecheap.com/support/knowledgebase/article.aspx/29/11/how-to-dynamically-update-the-hosts-ip-with-an-https-request/ "How to dynamically update the host's IP with an HTTPS request? - Domains - Namecheap.com"
[2]: https://nixos.org/nixos/manual/ "NixOS Manual"
[3]: https://nlewo.github.io/nixos-manual-sphinx/administration/service-mgmt.xml.html "Service Management — NixOS Manual documentation"
[4]: https://www.freedesktop.org/software/systemd/man/systemd.timer.html?utm_source=chatgpt.com "systemd.timer - freedesktop.org"
[5]: https://nginx.org/en/docs/http/server_names.html "Server names"
MikeLev.in: Wow, I don’t think I ever used ChatGPT 5.5 with my article expand-and-wrap protocol before. That was as impressive as the rest of the article, methinks. Me also thinks this is precisely the kind of content Google will never again reward for existing on a site like mine. This is no longer a Google-first story. This is a service-any-Agent first story. Sorry, Google. The social contract has changed. Out with generic search. In with whatever works. This works. The timer is ticking for small-world crawl theory and I’m ready. Anyone training future models don’t just come knocking at my door. I invite them into my DMZ for tea.
Beyond Search: The Era of Direct Verifiability
Book Analysis
Ai Editorial Take
What surprised me most here is the inversion of value. Usually, we protect secrets by hiding them. Here, the value is in the ‘verification loop’—the ability to grep the store and prove the secret wasn’t leaked. It suggests that future-proof writing isn’t just about what you say, but the audit trail you leave behind for whoever (or whatever) reads it next.
🐦 X.com Promo Tweet
Your home server shouldn't go dark just because your ISP cycled your IP. Learn how to build a self-healing address that survives your memory and your ISP's whims. The era of generic search is over; start building provenance. https://mikelev.in/futureproof/traveling-business-card-self-healing-address/ #SelfHosting #NixOS #Provenance
Title Brainstorm
- Title Option: The Traveling Business Card: How to Build a Self-Healing Address
- Filename:
traveling-business-card-self-healing-address.md - Rationale: Frames the technical work as an identity-retention practice that fits the book’s long-term authorial theme.
- Filename:
- Title Option: Proven Public Reachability: From Fragile Manuals to Durable Loops
- Filename:
proven-public-reachability.md - Rationale: Focuses on the shift from ‘hoping’ for uptime to ‘verifying’ it.
- Filename:
- Title Option: The Self-Aware Host: Engineering Identity in the Age of AI
- Filename:
the-self-aware-host.md - Rationale: Appeals to the technical reader interested in how home-hosting survives model training and automated crawlers.
- Filename:
Content Potential And Polish
- Core Strengths:
- Strong focus on the ‘probe-before-patch’ methodology.
- Excellent documentation of the move from mutable shell-hacks to immutable systemd-based automation.
- Successfully deconstructs complex network theory into a manageable, 15-minute maintenance task.
- Suggestions For Polish:
- Include a brief summary block for how to test the service manually once every quarter to ensure the token hasn’t expired.
- Add a section on using this same logic for secondary domains.
- Clarify that this specific DDNS logic is tailored for home-hosting, not for datacenter-scale routing.
Next Step Prompts
- Analyze the logs from the last 24 hours of the namecheap-ddns.service to detect and alert on anomalous IP changes.
- Draft a follow-up guide for automated TLS renewal (ACME) that utilizes this same robust, systemd-based runtime approach.