Skip to content

Installing the agent

This page answers two questions: what mikroscope install does to your router, and how you get the router back. install puts a 6.1 MiB agent image in a container on the router; uninstall removes it again. On RB5009UG+S+, RouterOS 7.24.2, , a scripted doctor → install → status → upgrade → uninstall round trip (make roundtrip, which passes --ephemeral to doctor, install and upgrade) left the router’s /export byte-identical, its # header lines aside. Every object install creates carries the comment mikroscope:<name> (managed by mikroscope), except the envlist and the image file, which cannot; the envlist carries the tag as its MIKROSCOPE_TAG entry. Nothing is written before it is listed; removal selects by that tag plus the object’s identity, never by pattern, and is verified by ownership counts.

Three things have to be true on the router, and the tool cannot make any of them true for you: a container-capable architecture on RouterOS 7.24 or later, the container package, and device-mode container=yes — the last one needs a physical button press or a power cycle. What the router needs covers all three. mikroscope doctor checks them read-only, in one ssh connect, and prints the exact command or physical step for anything missing.

One decision comes before the first install and not after it: where the agent image comes from. A checkout builds it, --agent-tar takes the one the release publishes, --remote-image has the router pull it, and plan --rsc writes a script that installs without this CLI at all. Four ways to install lays the four out.

  1. Gets the agent image. From a checkout, the CLI builds it: go build ./cmd/mikroscope-agent for linux/<arch> (--arch, default arm64) with CGO_ENABLED=0, packed into an image tar without Docker, and the build path is relative, so run it from the checkout. --agent-tar takes the tar the release publishes instead, and checks it before it uploads it. --remote-image skips this step entirely: the router pulls the image itself and nothing is uploaded. Four ways to install is the choice, with what each route needs.

  2. Prints the plan. One line of options (a token shows as token=(set), never its value), the tag, then every RouterOS command numbered, with the scp upload and its size before the container step. It ends with nothing above has been written yet. mikroscope plan and install --dry-run stop here, before any ssh connect.

  3. Runs doctor. Any missing prerequisite stops the install with N prerequisite(s) missing; nothing was written. --no-doctor skips this step.

  4. Asks write the objects above to the router? [y/N]. --yes skips the question.

  5. Asks the router about every step at once. One connect asks, for each step, whether mikroscope’s object is there and whether the effect exists under any other owner. A step already ours prints ok … (already present) and is skipped; an effect that exists without the tag stops the install, naming it; an absent one is created, one connect per write.

  6. Uploads the image and creates the container. The tar goes up with scp; one command then writes the envlist, adds the container, waits up to 15 s for the container to appear, then 3 s more (RouterOS extracts the image at add time), deletes the tar and starts the container. With --remote-image there is no upload and no tar to wait for or delete: the container is added with remote-image= and started. Either way the container is added with privileged=, which RouterOS knows from 7.24: on an earlier 7.x this is the step that fails, and --privileged=false is the way through.

  7. Probes the agent from your host and says which transport works: Reaching the agent.

A second install on a router where every step is already ours creates nothing and goes straight to the probe.

What install writes to your router

  • a veth
  • one address
  • one interface-list membership
  • one address-list entry
  • an envlist
  • the image tar, unless --remote-image has the router pull the image
  • the container

Every object carries the comment mikroscope:<name> (managed by mikroscope)

mikroscope plan prints every command before anything is written.

uninstall removes by exact tag plus identity, never by pattern, and fails naming the step if anything remains.

Where each of those objects lives, what the envlist carries and which container settings are written is on Where things go. Why two of them are firewall list memberships is on The two firewall traps.

The tag is the only thing a removal matches on, together with the object’s own identity: the veth by name, the address by interface, a list membership by list and interface, an address-list entry by list and address. Neither /container/envs nor /file carries a comment, so the envlist is signed by an entry MIKROSCOPE_TAG whose value is the exact tag, and the uploaded image counts as mikroscope’s only while that marker exists. The agent ignores the entry.

Every find quotes its address and port attributes. Unquoted, RouterOS parses them as typed values and the comparison with the stored one comes back empty — an unquoted dst-port=9123 matches nothing (verified on RB5009UG+S+, RouterOS 7.24.2, ).

Over ssh, RouterOS reports an error as text with exit status 0 and abandons the rest of a ;-joined line at the first one. A write that prints anything is therefore treated as a failure. If the container step fails after the upload, the uploaded tar is taken back (undo removed the uploaded …), because without the marker it would count as foreign forever. What else the installer refuses is on What the installer refuses.

What upgrade replaces

  • a new image and the container
  • the envlist, rewritten from the flags upgrade is given
  • network objects stay

Every object carries the comment mikroscope:<name> (managed by mikroscope)

mikroscope plan prints every command before anything is written.

mikroscope upgrade gets the image the same way install does — a build from the checkout, --agent-tar, or --remote-image and no image at all — checks that every step is present (otherwise nothing to upgrade: run install first), asks for confirmation, removes the container step, waits for RouterOS’s asynchronous removal, creates the step again with the new image and probes the agent. Unlike install, it prints no plan and runs no doctor: its prompt is the same write the objects above to the router? [y/N] with nothing listed above it. mikroscope plan with the same flags shows the container command it will write.

The envlist belongs to the container step, so upgrade writes it again from the flags upgrade itself is given. --port, --rate, --buffer, --memory-max, --mem-limit-mb, --capture-mb, --triggers, --floor-hz, --privileged, --ephemeral and --expose read no environment variable: pass them again or they return to their defaults. That is also the way to change them without touching the network objects. Two omissions are not harmless. An upgrade without --ephemeral re-creates the container with its image and root on the internal flash and start-on-boot=yes. An upgrade of an --expose install without --expose and without a token (--token or MIKROSCOPE_TOKEN) re-creates the agent with no token, while the two LAN firewall rules stay.

What uninstall removes

  • a veth
  • one address
  • one interface-list membership
  • one address-list entry
  • an envlist
  • the image tar, unless --remote-image has the router pull the image
  • the container

Every object carries the comment mikroscope:<name> (managed by mikroscope)

mikroscope plan prints every command before anything is written.

uninstall removes by exact tag plus identity, never by pattern, and fails naming the step if anything remains.

mikroscope uninstall runs the removals newest first. A removal that fails or prints anything is reported as skip with what the router said, and the rest continue. A step whose selector finds nothing prints gone just the same, which is why the count, not the removal output, decides. Then it asks the ownership count of every step in one connect, prints one line per step, and either ends with verified: nothing mikroscope created remains on the router or fails with uninstall left objects behind, naming the steps still present.

The container removal waits, because RouterOS does not: /container/remove returns before the container is gone, and a /file/remove of the image issued meanwhile did nothing, silently (RB5009UG+S+, RouterOS 7.24.2, ). So it stops the container, removes it, waits up to 20 s for it to vanish, retries the tar removal for up to 15 s, and removes the marker only once the file is gone. If it is not, the marker stays and the count says so.

mikroscope status prints the ownership count of every step, from one connect. When nothing is installed it ends with the verified line and probes nothing. Otherwise it probes the agent’s /healthz with a 3 s timeout and prints its version, rate, sequence and oldest sequence, uptime, slipped ticks and round trip, then one line about the board: whether this build knows how to turn the kernel’s port names (eth0, eth1, …) into RouterOS’s on it. Where it does not, the line asks for the measurement that would add the board; RouterOS ports and kernel names shows how to take it. If the agent does not answer, it prints agent: not reachable from this host with the error, and no board line; status still exits 0.

Terminal window
mikroscope doctor # read-only
mikroscope plan # every command, nothing written
mikroscope install [--ephemeral] # doctor, confirmation, writes, probe
mikroscope status # ownership counts + agent health
mikroscope upgrade # new image, container only
mikroscope uninstall # removes and verifies
mikroscope image --arch arm64 # the tar, for side-loading by hand
mikroscope plan --rsc --out install.rsc # the same writes, as a RouterOS script

--router takes user@host or an ssh config alias and is required by every verb that connects.

For these verbs, the flags that read their default from a variable are --router, --ssh-port, --ssh-key, --name, --veth, --subnet, --iface-list, --addr-list, --disk, --arch, --token, --lan-address, --agent-tar and --remote-image (MIKROSCOPE_ROUTER, MIKROSCOPE_SSH_PORT and so on); .env.example documents them. The CLI does not read .env itself:

Terminal window
set -a; . ./.env; set +a

Every value that reaches a RouterOS command is bounded before the first connection: names, disks, the architecture, memory syntax, the token’s characters, the port and rate ranges, the registry reference, the subnet, which must be an IPv4 /30 given at its network address, and --triggers, which the agent’s own parser decides on — an unknown condition, a bad threshold, a quote or a semicolon fails the verb with exit 2 and writes nothing. The agent parses TRIGGERS again when it starts, because an envlist can be edited by hand on the router; a value it rejects there makes it exit and log bad configuration. The full list is on Commands and flags and Environment variables.

Each ssh connect costs the RB5009 20–27 % CPU for its duration. So the CLI batches every read into one connect — doctor is one, status is one, the state questions of install are one — and each write takes one more, plus the scp upload. ssh is never a data path: record and forward reach the agent over HTTP or the RouterOS API.