## Motivation

I’m writing this because [the official are thin on details for this use case.(^They’d rather burn bandwidth telling you the 14 steps necessary to arm-twist Windows into this role by way of twenty-seven 800-by-400 color glossy screenshots with circles and arrows and a paragraph on the bottom of each one. Always keep telling yourself that Windows is the “easy” OS. You’ll come to believe it, eventually, no matter how much evidence mounts up to the contrary.)

It documents what worked for me.


## <a id="vms"></a>The Value of VMs

NetInstall needs to force I/O through a single network path under all conditions in order to do what it does. This might seem like an easy thing to accomplish, but then realize that NetInstall operates at a very low level, and there are multiple stages to the conversation, each of which may have different rules applied by elements of the intervening network.

Once upon a time, a typical computer would have only one “real” network interface plus the loopback interface, giving the OS running NetInstall only one logical choice for all outbound I/O, but modern systems are far more complicated. The one I’m typing this on has *twenty-five* network interfaces defined. While most of them are virtualized interfaces of some type,(^Software bridges, tunnels, phony VM NICs, etc.) each one is capable of changing the host OS’s routing table, affecting packet flow. If you think you know all the routing rules on such a system without careful inspection and testing, you’re probably wrong.

VMs give us a way to return to those good old days without affecting the host system: configure this NetInstall VM with a single virtual network interface, then bridge that to a specific hardware interface out on the host.

This recommendation holds even for those running Linux natively on the host system. While you _can_ run `netinstall-cli` directly in that case, setting a subordinate VM up for the sole purpose of forcing traffic down a single path is easier than trying to work out why NetInstall is failing atop the host OS. Like as not, the solution will involve reconfiguring the host to placate NetInstall, then reverting those changes when you’re done with NetInstall in order to get on with what you were doing before. If you instead abstract the problem away with a VM like this, the configuration is isolated to that one VM, requiring no changes to the host OS’s configuration.

Although I happened to prove this setup out with a CentOS Stream 9 VM running inside the latest version of the Parallels virtual machine manager atop the latest version of macOS, I don’t believe any of these details to be critical. You should be able to do the same thing atop Windows with Hyper-V running Debian, or atop Arch Linux with KVM running Ubuntu, or whatever.

The only key configuration choice is *bridging* the virtual network adapter to the one-and-only host-side Ethernet adapter that `netinstall-cli` will communicate over.(^In Parallels, you do that via the Devices → Network → Bridged Network → Ethernet menu choice. Other suitably powerful hypervisors — including VMWare and VirtualBox — can do the same via different command paths. I *suspect* Hyper-V is among that group, but I’ve had difficulties in the past with its bridged networking capabilities.) Success lies in avoiding cleverness like NAT, “shared” networking, automatic switching between Ethernet and WiFi, etc.


## <a id="router"></a>Router Configuration

Only one Ethernet port on your router will participate in an EtherBoot conversation. It might be marked “BOOT,” but if not, it’s generally the one that comes up as “ether1” in the default configuration. NetInstall will get stuck in the “`Waiting for RouterBOARD...`” step if you have the Ethernet cable plugged into the wrong port.


## <a id="hw"></a>Intervening Hardware

Another common way to fail with NetInstall is to put too much cleverness between the `netinstall-cli` host and the target device.

The ideal setup is to run a single cable from the `netinstall-cli` VM’s hardware port to the target’s Etherboot port. Nearly all hosts support [Auto these days, including MikroTik’s offerings.

Next-simplest is to interpose a dumb Ethernet switch.

Where you start to run risks is when you want to leave one or more smart switches between the VM host and the target device. A common way to fail here is to have a switch supporting DHCP Snooping in the way, which _will_ (not might, _**will**)_ break NetInstall. If the intervening switch is a MikroTik and you cannot go with one of the two options above, you need to mark the bridge port toward the VM host as to permit passing its BOOTP replies to the target device.

Never interpose anything still smarter, such as a router or a wireless AP. It’s doubtless possible to make it work, but you’re reading this guide to learn the easy path, right? Right.


## <a id="server"></a>Server Configuration

The key server-side change is that many Linux OSes ship with a firewall enabled which will block the ports `netinstall-cli` needs when communicating with the router. The tricky bit is, the minimum set of ports isn’t documented anywhere, that I can see. Red Hattish OSes(^RHEL, CentOS, AlmaLinux, Rocky Linux, Oracle Linux, Fedora…) use these days, where the commands to unblock the required ports are:

``` shell
sudo firewall-cmd --add-port bootps/udp
sudo firewall-cmd --add-port tftp/udp
sudo firewall-cmd --add-port 5000/udp
```

Other Linuxes use other firewall systems. Some still use raw `iptables` or `nft` commands, `ufw` is popular on Ubuntu, etc.

The first two required ports aren’t much of a surprise given the mention of “BOOTP” in the official docs, but I had to do a packet capture to work out that the last one was required. Without it, you’ll get stuck at the “`sendFile`” step.(^Another cause of the “stuck in `sendFile` symptom is attempting to send packages of the wrong architecture, such as AMD64 to a 32-bit ARM box or vice versa. If the product specs merely say “ARM” it means 32-bit.)

Now you can start the server:

``` shell
sudo netinstall-cli \
    -i enp0s5 \
    -r -s reset.scr \
    routeros-7.9-arm.npk \
    wifiwave2-7.9-arm.npk \
    container-7.9-arm.npk
```

The `enp0s5` value will vary by OS and virtual hardware configuration. On modern Linuxes, say “`ip link`” to get a list of possible names. For a VM, there are likely only two; pick the one that *isn’t* the `lo` interface.

Resetting the configuration is optional, and you can choose to accept the default config instead of what I’ve done here, but I wanted to show the option. The referenced `reset.scr` file is given [below](#script).

For routers with wired interfaces only, the base `routeros-*.npk` package is all you require, but for WiFi based routers, if you fail to at least include the appropriate wireless package, the default configuration is likely to come up improperly. Anything else you add to this is purely optional; I’m showing the `container` package as an example only.

I don’t know how critical it is to use the matching version of `netinstall-cli` when changing RouterOS versions, but while you’re downloading fresh NPKs, you might as well update it as well.


## <a id="script"></a>Default Router Configuration Script

My choice above to reset the configuration and apply a fresh configuration lets us do something fun and useful:

``` config
/interface bridge add name=bridge
/interface bridge port
add bridge=bridge interface=ether1
add bridge=bridge interface=ether2
add bridge=bridge interface=ether3
add bridge=bridge interface=ether4
/ip dhcp-client add interface=bridge
/user add name="foo" password="bar" group="full"
```

This is the `reset.scr` file referenced above, and although it is intended for any of MikroTik’s small WiFi routers, it should serve as a minimalist starting configuration for a wide range of devices.

The bridge setup configures it as a wired-only smart switch. Even if you have a WiFi device and you do want to use it as such, starting your configuration with the radios disabled is good practice. You want to set up the bands, radio power levels, and encryption secrets before exposing a wireless network that radiates outside your building.

The DHCP client lets you do several of these routers and connect them to a setup LAN without creating IP address collisions.

The default user step gets you around MikroTik’s new policy of resetting the `admin` user’s password to a random value printed in tiny text on a small sticker on the bottom of the router.(^To be fair, I fully support this policy; in fact, I proposed essentially what we eventually got on the forums long before they implemented it. I’m more annoyed that it took European Union legislation to arm-twist MikroTik into doing this than I am irritated by the fact that it now requires use of either a strong magnifier or a small trainable child before you can learn what that default password actually *is* on a device like the [hAP ax where it’s printed at 37 DPI using a 3pt dot matrix font.🙄 I’m showing you how to get around this password not because I believe in discarding this excellent security measure but to show that you don’t need to know the password to reset it. My recommendation is to treat this as a temporary password only; one of your first steps should be to give it your own long, random password, one not printed on any stickers anywhere.)

There’s one subtlety to this: `netinstall-cli` will yell if you name the script other than `*.scr`. I happen to think it ought to accept the `*.rsc` extension it uses on `/export`, but oh, well.


## <a id="wrapper"></a>Wrapper Scripts

I do not call `netinstall-cli` manually in practice. I rely instead on my [wrapper scripts](/dir?ci=trunk&name=netinstall), with which I can say things like:

``` shell
$ vi doit.in
$ ./update 7.17.2 arm64
$ cd 7.17.2/arm64
$ ./doit
```

The purpose of the first command is to have you examine the command template in your favorite text editor,(^…which is of course Vim aliased to `vi`, the only correct answer here) and if necessary, customize it to suit local needs. The version I ship is but a single example. It doesn't suffice even for all of my purposes, much less all of yours.

The second command pulls the latest version of `netinstall-cli` and all the NPKs for RouterOS version 7.17.2, then generates a "`doit`" script from that possibly-customized template.

The next two commands then fire that customized wrapper up per your instructions.

This may seem somewhat over-complicated until you come to a few key realizations:

1. It is useful to have canned configurations for different scenarios. This scheme lets you `cd` into a subdirectory and reapply that same configuration as many times as you need to. The proper setup for your core network switch is not the one you want for your APs, nor for your border gateway router. Therefore, once you get a particular configuration working, it may be smart to archive it for later use:

        $ mv 7.17.2/arm64 rb5009-gw-config-20250209

2. RouterOS upgrades can be risky. Having a few known-working `netinstall-cli` commands ready at hand can ease the stress if the upgrade fails. The wrong time to work out the correct command is when you've roached the Internet gateway, and now you're forced to use your smartphone on LTE to search the web for emergency help. You want your fallback plan in place _before_ the upgrade.


## <a id="myths"></a>Myths

You do **NOT** need to…

* disable the VM host’s WiFi(^The VM doesn’t even know about the host’s second interface, and we had you bind the VM to the Ethernet interface above regardless.)
* change your NetInstall VM’s IP address to use the subnet
* configure a static IP on the VM; even if you set it up using DHCP before reattaching the Ethernet cable to the target router, the Linux OS you’re using inside that VM *should* keep the address until the lease expires
* connect the router directly to the server’s copper Ethernet port; there merely needs to be an unimpeded L2 path between the two


## <a id="tips"></a>Tips

If you get the “`Key was rejected`” message, hit <kbd>Ctrl-C</kbd> to break out of `netinstall-cli`, then <kbd>Up-Arrow</kbd> and <kbd>Enter</kbd> to quickly restart it. I’ve seen this bypass the symptom when using a CentOS 8 Stream VM as the server.


## <a id="license"></a>License

This work is © 2023-2025 by Warren Young and is licensed under <a target="_blank" rel="license noopener noreferrer">CC BY-NC-SA 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"

<div style="height: 50em" id="this-space-intentionally-left-blank"></div>