From Dropbox to Daily Driver Part 9: The Closing Checklist, and What Actually Changed Since Part 5

Part 1 opened with a specific claim: that this build and Part 5 of Five Minutes and an Empty Port shared real DNA, a Raspberry Pi reachable through a Cloudflare Tunnel without a router port forward, and that the difference was never really the tooling. Eight parts later, that claim is either true or it isn’t, and the honest way to close this series is to actually check it rather than just assert it again. This part does three things: a closing checklist of every layer this build actually has, working, verified, not asserted; a look at which pieces of that design are Raspberry-Pi-specific versus which would work identically on any Linux box; and the direct, point-by-point comparison back to the dropbox this series keeps referencing but has never actually laid side by side with what got built here.

The closing checklist

Every line below was verified against the real device at some point in Parts 2 through 8, not assumed. Where a part number is missing, it’s because that layer doesn’t have one, either it’s out of scope by deliberate decision or it’s genuinely still open.

Identity and access. Password authentication is off at the sshd level, confirmed by a rejected login attempt in Part 3, not just by the config file saying so. Every login additionally requires a TOTP code from Part 4, verified by watching a real PAM-driven challenge succeed and fail in the server’s own journal. Since Part 5, none of that is even reachable without first clearing a Cloudflare Zero Trust Access policy scoped to one email address, tested from a network with no route back to the home LAN at all.

Network exposure. Zero inbound ports open on the home router, the entire remote-access path in Part 5 is an outbound-initiated tunnel. ufw denies all incoming traffic except SSH on the LAN itself, a redundant second layer behind the tunnel rather than the only layer, verified with ufw status verbose in Part 3 and again independently by the Ansible role in Part 8.

Patching. Debian’s own package channel patches itself via unattended-upgrades, configured and its actual scope, not just its existence, checked in Part 3. Raspberry Pi’s own kernel and firmware packages are explicitly outside that scope and still need a manual apt upgrade, an honest gap rather than a glossed-over one. So is a gap Part 3 flagged but didn’t close: the Cloudflare Tunnel client from Part 5 and the Wazuh agent from Part 7 were both installed from a downloaded .deb, not an apt repository, so neither one gets touched by unattended-upgrades either. The tunnel client is this device’s entire remote-access path, and the agent is its entire detection layer, and both currently get updated the same way the very first install happened, by hand, whenever someone remembers to check for a new release.

Detection. A Wazuh agent has been reporting to a dedicated manager VM since Part 7, with default rules already catching SSH, PAM, and sudo events with zero custom configuration, confirmed against 225 real log hits generated by actually using the device. Wazuh’s SCA module runs a CIS-based configuration audit on a 12-hour interval, currently scoring 54%, up from an initial 38%, with every remaining failure sorted into one of four explained categories rather than left as unexplained noise.

Reproducibility. Since Part 8, the base-hardening layer from Part 3 exists as an Ansible role, not just a blog post someone would have to re-read and manually replay. It’s been run for real against a second, independent Pi, verified against the live system rather than trusted from the play recap, and proven idempotent on a second run with zero changes reported. If the microSD card in this device fails tomorrow, the real answer is now “reflash and run the playbook,” not “hope it doesn’t happen,” which was the honest but unsatisfying answer Part 1 gave to the same question.

What’s still genuinely open, not fixed, not forgotten: the Pi is still on a DHCP lease rather than a static address, harmless given the tunnel is identified by credentials rather than IP, but untidy. AppArmor is active in the kernel as of Part 7 but has no real per-service confinement written for sshd or the Wazuh agent, the actual attack surface on this device, that’s explicitly future series material rather than something this series claims to have finished. And the Ansible coverage from Part 8 is one role out of what a full reproduction would need, TOTP, the tunnel, and the Wazuh agent itself all still get configured by hand if this device had to be rebuilt today, not by a second and third role.

Two more gaps are worth naming plainly rather than letting the checklist above pass for exhaustive. Detection, as built, is dashboard-only. Parts 6 and 7 get Wazuh correctly classifying logins, sudo events, and configuration drift, but nothing in this series wires any of that to a phone or an inbox. A real alert currently sits in the dashboard until someone opens it, which isn’t the same claim as being told about it. Wazuh supports email and webhook integrations for exactly this; none of them are configured here.

And neither wazuh-manager nor ansible-ctrl, the two pieces of pve infrastructure this series stood up in Parts 6 and 8, have a backup path of their own. The restic-based backup already running elsewhere in this homelab is built around this site and its own MCP server specifically, not pve infrastructure in general, so it doesn’t reach either of these VMs automatically. A manager holding months of alert history and a control node holding an Ansible Vault file with this device’s own become-password are both worth more than “rebuild it from this blog post if pve ever loses the disk,” but that’s honestly all either one currently has.

What’s Raspberry Pi-specific, and what isn’t

It’s worth being precise about this, because a series with “Raspberry Pi” in every title could easily read as more hardware-specific than it actually is, and that would undersell the parts of this build worth reusing elsewhere.

Genuinely Pi-specific: the storage risk that shaped Part 1’s decision to prioritize Ansible reproducibility, a microSD card wearing out under continuous journald and monitoring-agent writes, is a real constraint of this specific class of hardware, not a general Linux concern on a box with a proper SSD or NVMe. The sshd_config.d versus cloud-init drop-in conflict in Part 3, and its SCA-flagged echo in Part 7, exists because Raspberry Pi Imager’s customisation screen writes a cloud-init drop-in on first boot, a mechanism specific to how Imager provisions a device, not a property of Debian or SSH generally.

Everything else genuinely isn’t hardware-specific at all. Key-only SSH plus a PAM-driven TOTP second factor is standard OpenSSH and standard libpam-google-authenticator, identical on any Debian or Ubuntu box. ufw is Debian’s own firewall front-end. Wazuh’s agent runs on the same handful of architectures as most enterprise Linux monitoring tooling and its ruleset for sshd/PAM/sudo doesn’t care what’s underneath it, the same CIS-based SCA policy exists for most major distros, cis_debian13.yml here would be a different filename on Ubuntu or RHEL and functionally the same audit. The Ansible role in Part 8 targets a pis group in the inventory purely by convention; nothing in base_hardening’s actual tasks reads anything Pi-specific, it would run against any Debian-family host unchanged. And the access model in Part 5, an outbound tunnel gated by an identity check in front of the service rather than a port forward and a service-level auth prompt, is an architecture choice that has nothing to do with what’s plugged into the other end of the tunnel.

The practical takeaway for anyone reading this series with a different device in mind: swap the storage-risk reasoning and the Imager-specific cloud-init gotcha for whatever’s actually true of your hardware, and the rest of this build transfers close to verbatim.

Back to Part 5, for real this time

Part 1 made the claim that this build and the dropbox share real DNA and differ mainly in authorization, visibility, and what sits around the core mechanism. Here’s that comparison actually made, rather than asserted and left for later.

Dropbox (empty-port Part 5)This build
Core mechanismCloudflare Tunnel, outbound reverse SSH via autosshCloudflare Tunnel, same core primitive
AuthenticationWhatever got the tunnel up fastest for a five-minute exerciseKey-only SSH, plus a mandatory TOTP second factor
Access gate in front of the tunnelNone, tunnel terminates straight at SSHZero Trust Access policy, identity-checked before the connection is handed off
VisibilityNone by design, meant to be planted and forgottenWazuh agent, default rules catching every login and sudo, zero custom config needed
Configuration drift detectionNoneSCA module auditing against CIS benchmarks every 12 hours
RecoverabilityA disk image, or start overAn Ansible role, proven idempotent against an independent second device
Threat modelProving a mechanism works during a physical-access exerciseOpportunistic scanners and credential-stuffing against a device someone actually depends on

The honest reading of that table isn’t “the dropbox was insecure and this build fixed it,” they were never trying to solve the same problem. It’s that identical tooling produces a completely different security posture depending entirely on what gets built around it, which was the actual point Part 1 was making before there was anything built yet to point at. Now there is.

Where this series actually leaves things

Nine parts, one physical Pi, reflashed once. It went from a five-minute exercise proving a mechanism could work, to a daily-driver device with layered authentication, a real detection layer, and a reproducibility story that doesn’t depend on a single microSD card surviving indefinitely. The open items above are real and staying open on purpose, not oversights: this series was never trying to claim a finished, closed state, just an honestly verified one at each step along the way. If any of them get closed later, whether that’s a static IP, per-service AppArmor confinement, alert delivery that reaches further than a dashboard, or the rest of this build folded into Ansible, that’s material for whatever comes next, not a promise this series is making about when.