# What the router needs

The architecture, the container package and the device-mode step that needs a hand on the router, plus what your own host needs, and how `doctor` checks each one.

Source: https://jmrplens.github.io/mikroscope/install/prerequisites/

This page lists what has to be in place before `mikroscope install` can write
anything: three things on the router the tool cannot do for you, the resources
and firewall lists it checks, and what the host you run it from needs.
`mikroscope doctor` checks all of it read-only, in one ssh connect, and prints
the exact command or physical step for anything missing.

> **One step needs your hands on the router**
>
> `device-mode container=yes` cannot be set remotely. After the command, RouterOS waits five minutes
> for someone to press the reset or mode button, or power-cycle the device. No ssh session, API call
> or option of this tool can do that step. Plan to be at the router.

## A container-capable device

The router must run RouterOS 7.24 or later on one of three architectures:
arm64, arm (32-bit RouterOS on the hEX refresh line) or x86_64. Not MIPS, not
TILE.

7.24 is the floor because the container step writes `privileged=`, an attribute
RouterOS added in that release. `doctor` prints the version and checks nothing
against it, so on an earlier 7.x the install runs as far as the container and
fails there with RouterOS's error about an unknown `privileged` parameter —
and a write that prints anything counts as a failure, so the install stops and
takes the uploaded tar back with it. `--privileged=false` is the way through,
at the price of everything the container's user namespace hides:
[What privileged buys](/mikroscope/limits/privileged/) lists it.

Tell the CLI which one with `--arch`: `arm64` (the default), `arm` or `amd64`.
`doctor` compares it with the router's `architecture-name` and, on a mismatch,
names the flag to re-run with.

| Your device                                        | `architecture-name` | `--arch` | The agent build             |
| -------------------------------------------------- | ------------------- | -------- | --------------------------- |
| RB5009, CCR2004, hAP ax³ and other 64-bit ARM       | `arm64`             | `arm64`  | `linux/arm64`               |
| hEX Refresh / hEX S (2025), any EN7562CT board      | `arm`               | `arm`    | `linux/arm/v5`              |
| Other 32-bit ARM (hAP ac², hAP ax², …)              | `arm`               | `arm`    | `linux/arm/v5` or `v7`      |
| CHR and x86 RouterOS                                | `x86_64`            | `amd64`  | `linux/amd64`               |

**32-bit ARM is two things, not one.** MikroTik's container documentation
states that devices with the EN7562CT CPU — the hEX Refresh line — "support
only arm32v5 container images", while its other 32-bit ARM boards run an ARMv7
userland. An ARMv5 binary runs on both; an ARMv7 one does not run on the first,
and the way it fails is an `exec format error` in the container log after a
successful install. So `--goarm` defaults to **5**, the level that starts
everywhere, and the image declares the matching variant. `--goarm 7` builds the
ARMv7 one for a board where that instruction set is wanted; what the difference
costs has not been measured, because this project has no ARM hardware.

`--remote-image` makes the question go away: the published index carries
`linux/amd64`, `linux/arm64`, `linux/arm/v7` and `linux/arm/v5`, and the router
picks its own.

## The container package

Download the `container` package for your architecture and RouterOS version
from mikrotik.com, upload it to the router and reboot; then
`/system/package/enable container`. That is the fix `doctor` prints, and it
counts the package as present only when it is installed and not disabled.

## device-mode container=yes

MikroTik gates containers behind a physical step:

1. Run, on the router's console:

   ```text
   /system/device-mode/update container=yes
   ```

2. The console answers:

   ```text
   update: please activate by turning power off or pressing reset or mode button in 5m00s
   ```

3. Within those five minutes, press the reset or mode button, or power-cycle the router.
   If nobody does, the change is cancelled.

After three failed attempts the router says
`too many unsuccessful attempts … to reset attempt-count` and needs a power
cycle before it accepts another.

## What doctor checks

`doctor` prints `device:` with the board, the RouterOS version and the
architecture, then one line per check marked `ok` or `MISSING`, with what it
found in parentheses and, for a missing one, a `fix:` line. It ends with
`doctor: every prerequisite is met`, or fails with
`N prerequisite(s) missing; nothing was written`. `install` runs the same checks
first unless you pass `--no-doctor`.

The checks doctor runs:

| Check, as printed | Passes when | The fix it names |
| --- | --- | --- |
| registry-url is https://<host> | with `--remote-image`, `/container/config registry-url` names the reference's registry host. Without `--remote-image` doctor does not ask: the setting is global to the device and mikroscope never writes it | `/container/config/set registry-url=https://<host>` on the router, which applies to every container on it, or install from a tar with `--agent-tar` |
| container package installed and enabled | a `container` package exists with `disabled=no` | download, upload, reboot; then `/system/package/enable container` |
| device-mode container=yes | `/system/device-mode` reports `container=yes` | `/system/device-mode/update container=yes`, then the reset or mode button, or a power cycle, within 5 minutes |
| architecture matches --arch <arch> | the router's `architecture-name` is the one `--arch` maps to (`arm64`, `arm`, `x86_64`) | re-run with the `--arch` it names |
| free memory ≥ <--memory-max> | `free-memory` is at least what `--memory-max` asks for, 64 MiB by default | free memory on the router, or ask for less with `--memory-max` |
| free flash ≥ <size> (image tar + extracted root) | without `--disk`: `free-hdd-space` is at least twice the image plus 4 MiB | free flash, or install with `--disk tmpfs` or `--ephemeral` where a tmpfs disk exists |
| disk <disk> exists | with `--disk` or `--ephemeral`: a disk with that slot exists; its free space is not checked | `/disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs` for a RAM disk, or name an existing disk with `--disk` |
| interface list <list> exists (raw rule trap) | the `--iface-list` list (default `LAN`) exists | `/interface/list/add name=…`, or pass the list your `in-interface-list=!…` drop rule uses |
| address list <list> has entries (raw rule trap) | the `--addr-list` list (default `LANs`) has at least one entry | pass the list your `drop local if not from default IP range` rule uses; an empty list is fine only if there is no such rule |
| veth name <veth> is free or ours | always reported `ok`, with the count found | none: a collision is caught by `install` itself |

The flash check uses the real tar size under `install`. `doctor` on its own
assumes a 7 MiB image, so it asks for 18.0 MiB. Twice the image because the tar
and the root extracted from it are on the disk together until `install` deletes
the tar; with `--remote-image` no tar is uploaded, so the check asks for the
4 MiB of headroom alone. The memory threshold follows `--memory-max`: it asks
for at least what that flag asks for, which is 64 MiB by default, so
`--memory-max 128M` on a router with 70 MiB free is caught here rather than by
a container that will not start.

The `registry-url` check runs only with `--remote-image`, and only when the
reference carries a registry host. `/container/config` is global to the device
and shared with every other container on it, so mikroscope reads that setting
and never writes it. RouterOS ships it as `https://registry-1.docker.io`, so the
Docker Hub reference, `--remote-image jmrplens/mikroscope-agent:1.0.0`, needs
nothing set there on an untouched router, and the GHCR reference is the one that
needs the setting changed first.
[Four ways to install](/mikroscope/install/routes/#a-registry-pull) has the
command to set it by hand.

The two list checks exist because of two raw firewall rules that drop every
packet a container sends; [The two firewall traps](/mikroscope/install/firewall/)
explains them. `doctor` marks an empty address list missing even on a router
that has no such rule; there, `--no-doctor` is the way past it, and it skips
every other check with it.

## What your host needs

- **ssh to the router as a user that can write**, your own admin access. The CLI
  runs the system `ssh` with `BatchMode=yes` and `ConnectTimeout=15`, so it
  cannot answer a password prompt: use a key or an ssh agent. `--router` takes
  `user@host` or an ssh config alias; `--ssh-port` and `--ssh-key`
  (`MIKROSCOPE_SSH_PORT`, `MIKROSCOPE_SSH_KEY`) fall back to your ssh
  configuration when unset. The image goes up with `scp`, on the two routes
  that upload one. The RouterOS script route needs no ssh at all.
- **An agent image, by one of four routes.** The CLI can build one
  (`go build ./cmd/mikroscope-agent` from a checkout, which needs Go 1.27), take
  the tar the release publishes (`--agent-tar`, no toolchain and no checkout),
  or let the router pull the image itself (`--remote-image`, nothing uploaded).
  The fourth route needs no CLI on your machine at all:
  `plan --rsc` writes a RouterOS script you paste into the router.
  [Four ways to install](/mikroscope/install/routes/) has the commands, what
  each route needs and how to verify a download.
- **A way to reach the agent** once it runs: a route to the container's /30
  through the router, the relay over the RouterOS API, or `--expose`. See
  [Reaching the agent](/mikroscope/install/reaching-the-agent/).
- **A RouterOS API user**, only for the relay transport, `--log-markers` and the
  collector's API tier. It stays on your machine; its policy is on
  [The API user](/mikroscope/security/api-user/).

> **Untested**
>
> An install on arm or x86_64: every installation so far ran on one arm64 RB5009, and the hEX S that
> will test 32-bit RouterOS has not arrived. Any RouterOS other than 7.24.2. The container step
> writes `privileged=`, which RouterOS added in 7.24, and envlist entries with `key=`, where `name=`
> fails on 7.24.2; how an earlier 7.x takes either was not tried.

## See also

- [Installing the agent](/mikroscope/install/): what `install` does once these are in place.
- [Four ways to install](/mikroscope/install/routes/): the four routes the agent image can take to
  the router, and how to verify a download.
- [The two firewall traps](/mikroscope/install/firewall/): why the two list checks exist.
- [Where things go](/mikroscope/install/layout/): the disk the flash check is about, and
  `--ephemeral`.
