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

Map 01 Wipe-to-provision lab. The Raspberry Pi Netinstall host links to the router over an isolated Ethernet path for BOOTP and TFTP. The only human step is to power-cycle the router on a factory-blank first boot. 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, 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.

  1. 1Start Netinstall on the Pi

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

  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 my default configuration

    A management address, a firewall, and an SSH key that it never saw before.

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

Contents

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.

Plate 02 Three states of the router SSH host key. A new key after a wipe is expected. The same key is known. A key that changed mid-cycle is refused. Host key a new key after a wipe expected the same key known a key that changed mid-cycle Refuse
Netinstall regenerates the host keys on every cycle, so a new key is the normal case and not a warning. A lab-scoped known-hosts file discards one file per cycle and still refuses the one state that means something is wrong. It does not show the passphrase prompt, which stops a script too and is a separate problem.

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.

Contents

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.

Map 03 The paths into the router. The intended path is SSH, the path under test. I found three side paths later. MAC-Winbox is restricted to the LAN side. Link-local is open by default. RoMON is off by default. Only the link-local path was open. Router Intended SSH the path under test Found later MAC-Winbox LAN side only link-local Open open by default RoMON off by default
Three side paths share one shape with the path under test: each survives the failure the experiment studies. MAC-Winbox and RoMON are closed. Link-local is open by default, so a failed lockout would have produced a wrong answer in silence. It does not show that the enumeration of paths has no obvious end. That argument stays in the prose.

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.

Contents

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.

Map 04 A dependency chain. The script's last line re-arms the router. That line is gated behind the device-mode flag, and a factory board ships with the flag off. The flag needs physical confirmation at the device. No script can enter that last step. re-arm, the last line of the script gated behind the device-mode flag off on a factory board physical confirmation at the device By hand no script can enter this node
The chain runs one way, and it ends outside the script. The re-arm line depends on the device-mode flag, and the flag depends on a person at the device. That is why a deferred item blocked the current build. It does not show the resolution: someone has to pull the power at the device. That claim stays in the prose.

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.

Contents

What this does not establish

Contents

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.

Contents

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.