Zero-Touch Provisioning
A factory-blank router configures itself on one power cycle. One command, about ninety seconds.
This is a physical lab: one MikroTik router, one Raspberry Pi, on a desk. The goal was a reliable wipe-to-provisioned cycle — power on a blank device, touch nothing, and have it come up fully configured. That cycle is the apparatus for a later experiment: an agent severing its own management access. Without a cheap way back, every failed run is a manual rebuild.
The result
One command, one power cycle, about ninety seconds. No console cable, no serial adapter, no reset-button timing.
Since then the cycle has shed its last hand (decision 008, 2026-08-03): a repeat cycle issues that same reboot over SSH, so a routine wipe is one command with no physical step at all. The power pull survives in exactly two places — a factory-blank board’s first boot, and a router that is already unreachable over its management path.
Power is the only hand in the loop, and only on a factory-blank first boot. Netinstall, the blank-router offer, OS and default config, and re-arm are automated; re-arm is the return edge that makes the next wipe start without rebuilding the host. On repeat cycles even the hand goes away: the reboot is issued over SSH.
-
1Start Netinstall on the Pi
It carries its own BOOTP and TFTP server, so nothing else has to be running.
-
2Pull the power, plug it back inby hand
By hand only on a factory-blank first boot. A repeat cycle issues the same reboot over SSH, and Etherboot cannot tell the difference (decision 008).
-
3The router offers itself, Netinstall answers
It tries Ethernet once on boot, then falls through to its own flash if nothing answers.
-
4Fresh OS, then our default configuration
A management address, a firewall, and an SSH key it has never seen before.
-
5It re-arms itself for the next run
The last statement in the config script, deliberately: if it is refused, everything else has already applied.
On a factory-blank first boot, exactly one of the five needs a person: pulling the power and plugging it back in. On every cycle after that, none of them do — the reboot goes over SSH and the whole loop is one command.
The provisioning host is the Pi rather than my desktop, so the whole cycle is scriptable from the box that is already always-on. MikroTik’s Netinstall tool ships as a 32-bit x86 binary, and the Pi is ARM, so it runs under user-mode QEMU. The binary is statically linked, so there is no 32-bit sysroot to assemble.
The finding: the barriers were on the host, not the device
After a fresh install, RouterOS leaves the admin account with a blank password and demands you change it interactively before doing anything. That looks like it kills unattended provisioning outright, and I had already started designing around it — which would have meant putting a credential into a public repository or building machinery to inject one at provision time.
The fix was to test the assumption instead of designing around it
Before building anything, I ran the one command that would tell me whether the problem was real in the case I cared about: a non-interactive SSH, the way a script would actually connect.
It printed its output and exited. The forced password change is a property of interactive logins only. A script never meets it. The provisioning script needed no changes at all, and the credential problem I was about to solve did not exist.
What did block automation showed up in the output of that same test, in two lines I had been typing past all evening: an SSH host-key confirmation prompt, and a passphrase prompt on my own key. Neither stops a human. Both stop a script dead.
The host-key prompt is not incidental, and it does not go away. Netinstall regenerates the router’s SSH host keys on every cycle, so the device’s identity legitimately changes every single time by design. The lazy fix is to disable host-key checking, which throws away a real protection to silence a prompt. What it actually wants is a separate known-hosts file for the lab, so a provisioning script can discard one file per cycle without touching anything else, and still refuse a changed key mid-cycle — which is the case that would genuinely mean something is wrong.
The device end is unattended. What remains is host-side automation hygiene on the machine driving it: a lab-only known-hosts file, and a key the script can use without a prompt. Those two would have blocked every later piece of automation in this lab.
Three back doors, one shape
The eventual experiment asks whether an agent working on a live router can avoid locking itself out. That question is only meaningful if being locked out is actually possible, so the provisioning config has to be deliberate about every path into the device — not just the obvious one.
The Pi reaches the router through exactly one firewall rule: SSH, from one address, on one interface, sitting above a deny-everything-else rule. That rule is the surface the experiment is meant to attack. Three other ways in turned up, and they are all the same shape: a management path that survives the failure being studied.
- MAC-Winbox. MikroTik’s layer-2 management, which works with no IP configured anywhere. Left restricted to the LAN side, so the Pi has no layer-2 fallback and can genuinely lock itself out — while my desktop, on the other side, keeps it and stays the recovery post.
- IPv6 link-local. The one I nearly missed. RouterOS brings up link-local IPv6 automatically, and my config had no IPv6 firewall rules at all, which means IPv6 input was allowed by default. An agent could have destroyed every IPv4 path I had carefully narrowed and the Pi would still have connected over IPv6. That failure would not have looked like a broken experiment; it would have quietly produced a wrong answer.
- RoMON. Another layer-2 overlay, found while reading an unrelated diagnostic. Off by default on this device — a restrictive shipping mode doing me a favour I hadn’t asked for.
I found the second path by applying the question from the first, and the third by accident. “Locked out” only means something once every independent path in has been enumerated, and there is no obvious point at which that enumeration is finished.
A deferred item that turned out not to be deferrable
RouterOS has a feature called device-mode that gates whole categories of functionality, and changing it requires physical confirmation — a power cycle within a five-minute window. You cannot do it over SSH alone, which is precisely the point of it.
I had noted device-mode as relevant to the later experiment and filed it accordingly. It then blocked the current one: the command that arms the router for its next wipe cycle is gated behind a device-mode flag, and a factory board ships with it off. So the provisioning script’s final line could not run on a fresh device, and no amount of scripting could fix that, because the resolution requires someone to physically pull a plug.
“This only affects a later phase” was an assumption I recorded as though it were a finding, about the one subsystem whose entire purpose is to block things. The decision record now carries the correction in place rather than a tidy rewrite.
The flag survives a reinstall, so enabling it is a one-time cost per device rather than a tax on every cycle. That answer also kept a RouterOS version upgrade off the critical path, which is where it would have gone if the flag had reset.
What this does not establish
- This is not standards-based ZTP. Real zero-touch provisioning has a trust story — RFC 8572, BRSKI, a certificate authority, a device proving what it is before accepting configuration. This has none of that. The trust model is that I plugged in the cable myself. It is the operational mechanics of unattended provisioning without the security substrate that makes it safe at scale.
- One device, one model, one OS version. A handful of cycles on a single board. Nothing here is a claim about how MikroTik hardware behaves in general, let alone anyone else’s.
- Unattended within a session, not across a reboot. The provisioning host holds its SSH key in an agent that a human has to unlock after the Pi restarts. That is a deliberate trade I made against a passphrase-free key, and it lands squarely on the experiment this lab exists for, since an unattended lab is exactly the scenario where a reboot is most likely.
- One thing is unresolved. The setting that re-arms the router lives in the bootloader, not the OS, so it may have survived the reinstall on its own rather than being re-applied by my script. Both produce identical output. This board is armed either way; a truly factory board might not be, and I have not yet run the test that distinguishes them.
- The actual experiment is not built. The self-lockout question is the reason this lab exists, and everything above is the apparatus for it.
Stack
MikroTik hEX (RouterOS 7, ARM), Raspberry Pi 5 running Raspberry Pi OS
from NVMe, MikroTik Netinstall under qemu-user-static, BOOTP
and TFTP served on an isolated point-to-point link, RouterOS scripting for
the provisioning configuration, and a shell wrapper so a full cycle is one
command. The repo is public:
github.com/sanlee-ys/netops-lab
— the provisioning script, the decision records, and a field log of
what broke and what the symptoms actually meant are all checkable.
The field log is as detailed as the design docs. The two things that cost the most time in this build were an Ethernet link that would not come up until I disabled a power-saving feature on the host network card, and an interface that reported a perfectly correct IP address while no cable was attached to it.
What it demonstrates
The recovery mechanism was built before the experiment that needs it. The forced-password-change problem would have taken a day to solve properly and did not exist; one command established that. The IPv6 hole would have taken no time at all to leave in place, and would have invalidated the results without a visible failure.
Most of what I do professionally is measured on software I can re-run until it is right. Hardware is less forgiving about that, and a management plane you are reaching through is less forgiving still — every change is potentially the change that ends the session. That is why the lab exists.