← Back

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.

Wipe-to-provision lab: Raspberry Pi Netinstall host linked to the router over an isolated Ethernet path for BOOTP and TFTP; the only human step, needed on a factory-blank first boot, is power-cycling the router; the router’s final re-arm step closes the loop so the next wipe can start. Power By hand Pi Netinstall Router wipe → cfg BOOTP / TFTP re-arm closes the next wipe

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.

  1. 1Start Netinstall on the Pi

    It carries its own BOOTP and TFTP server, so nothing else has to be running.

  2. 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).

  3. 3The router offers itself, Netinstall answers

    It tries Ethernet once on boot, then falls through to its own flash if nothing answers.

  4. 4Fresh OS, then our default configuration

    A management address, a firewall, and an SSH key it has never seen before.

  5. 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.

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

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.