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 it comes up fully configured. That cycle is the apparatus for a later experiment: an agent that severs 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 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, the OS and default config, and the re-arm all run automatically. Re-arm is the return edge that starts the next wipe with no rebuild of the host. On repeat cycles even the hand is not needed: a repeat cycle issues the reboot over SSH.
-
1Start Netinstall on the Pi
It carries its own BOOTP and TFTP server, so nothing else has to run.
-
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 my default configuration
A management address, a firewall, and an SSH key that it never saw before.
-
5It re-arms itself for the next run
The last statement in the config script, deliberately: if the router refuses it, everything else already applied.
On a factory-blank first boot, exactly one of the five needs a person. That person pulls the power and plugs 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. It demands that you change the password interactively before you do anything. That looks like it makes unattended provisioning impossible, and I already began the design around it. That design would need a credential in a public repository, or machinery that injects 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. The device’s identity therefore changes every single time, legitimately and by design.
The lazy fix is to disable host-key checking. That removes a real protection to silence a prompt. What the lab actually wants is a separate known-hosts file, so a provisioning script discards one file per cycle and touches nothing else. It still refuses 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 that drives 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 on a live router can avoid a self-lockout. That question is only meaningful if a lockout is actually possible. So the provisioning config has to be deliberate about every path into the device, and not just the obvious one.
The Pi reaches the router through exactly one firewall rule: SSH, from one address, on one interface, above a deny-everything-else rule. That rule is the surface the experiment is meant to attack. I found three other ways in, and they are all the same shape: a management path that survives the failure under study.
- MAC-Winbox. This is MikroTik’s layer-2 management, and it works with no IP configured anywhere. I left it restricted to the LAN side. The Pi therefore has no layer-2 fallback and can genuinely lock itself out. My desktop, on the other side, keeps it and stays the recovery post.
- IPv6 link-local. This is the one I nearly missed. RouterOS brings up link-local IPv6 automatically, and my config had no IPv6 firewall rules at all. So IPv6 input stayed open by default. An agent could have destroyed every IPv4 path I narrowed so carefully, 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. This is another layer-2 overlay, and I found it while I read an unrelated diagnostic. It is off by default on this device. A restrictive shipping mode helped me here, and I did not ask for it.
I found the second path when I applied the question from the first. I found the third by accident. “Locked out” only means something after an enumeration of every independent path in, 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, and it gates whole categories of functionality. A change to device-mode requires physical confirmation: a power cycle within a five-minute window. You cannot make that change over SSH alone, which is precisely the point of it.
I recorded device-mode as relevant to the later experiment, and I 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. A factory board ships with that flag off. So the provisioning script’s final line could not run on a fresh device. No script 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. It was 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. I therefore pay to enable it one time for each device, and not on every cycle. That answer also kept a RouterOS version upgrade off the critical path. A flag that reset would have put that upgrade on the critical path.
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, and a device that proves what it is before it accepts 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. Nothing here is a claim about any other maker’s hardware.
- 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. I made that trade deliberately against a passphrase-free key. It affects the experiment this lab exists for, because 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. It may have survived the reinstall on its own, and not from a re-application by my script. Both produce identical output. This board is armed either way. A truly factory board might not be armed, 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 with Raspberry Pi OS
on NVMe, MikroTik Netinstall under qemu-user-static, BOOTP
and TFTP 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.
It carries the provisioning script, the decision records, and a field log
of what failed and what the symptoms actually meant. All of them are
checkable.
The field log is as detailed as the design docs. Two things cost the most time in this build. The first was an Ethernet link that would not come up until I disabled a power-saving feature on the host network card. The second was an interface that reported a perfectly correct IP address while no cable was attached to it.
What it demonstrates
I built the recovery mechanism before the experiment that needs it. The forced-password-change problem would have taken a day to solve properly, and it did not exist. One command established that. The IPv6 hole would have taken no time at all to leave in place, and it 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. A management plane you reach through is less forgiving still. Every change is potentially the change that ends the session. That is why the lab exists.