# Installing the agent

What `mikroscope install` does to a RouterOS device and in which order, and how `upgrade` and `uninstall` change or remove it without touching anything they did not create.

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

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, 2026-09-12, 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.

## Before the first install

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](/mikroscope/install/prerequisites/) 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](/mikroscope/install/routes/) lays the four out.

## What install does, in order

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](/mikroscope/install/routes/)
   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](/mikroscope/install/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](/mikroscope/install/layout/). Why
two of them are firewall list memberships is on
[The two firewall traps](/mikroscope/install/firewall/).

## How ownership is decided

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, 2026-09-11).

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](/mikroscope/security/installer/).

## Upgrade

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

## Uninstall

**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, 2026-09-11). 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.

> **Uninstall with the flags you installed with**
>
> `uninstall`, `status` and `upgrade` build their selectors from their own flags, not from anything
> stored on the router. Pass the same `--name`, `--veth`, `--subnet`, `--iface-list`, `--addr-list`,
> `--disk` or `--ephemeral` and `--port` that `install` got. For an install with `--remote-image`,
> pass that too: the container's ownership count is taken by what it was created from, and a
> remote-image install has no tar to count. For an install with `--expose`, pass
> `--expose --lan-address … --token …` as well: without `--expose` the plan has no firewall steps,
> so `uninstall` neither removes the two rules nor counts them.

## Status

`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](/mikroscope/reference/port-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.

## Day to day

```sh
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.

## Flags and the environment

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:

```sh
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](/mikroscope/reference/cli/) and
[Environment variables](/mikroscope/reference/environment/).

## What ssh costs the router

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.

> **Untested**
>
> Reboot survival of a persistent install: the reference router is production and is not rebooted
> for tests. The byte-identical round trip on any device other than the RB5009, on any RouterOS
> other than 7.24.2, or with install and upgrade run without `--ephemeral` (the scripted run passed
> it to doctor, install and upgrade, not to status or uninstall). The round trip has not been run
> against the current container settings: `privileged=yes`, `memory-max=64M`, and the envlist
> entries `MEM_LIMIT_MB`, `CAPTURE_MB`, `TRIGGERS` and `FLOOR_HZ`; it ran with `memory-max=32M`.

## See also

- [What the router needs](/mikroscope/install/prerequisites/): the architecture, the package and
  the device-mode step that needs your hands on the router.
- [Four ways to install](/mikroscope/install/routes/): a checkout, the published tar, a registry
  pull, or a RouterOS script.
- [Where things go](/mikroscope/install/layout/): disks, the envlist and the container settings.
- [Reaching the agent](/mikroscope/install/reaching-the-agent/): the probe, and direct, relay and
  `--expose`.
- [What the installer refuses](/mikroscope/security/installer/): objects it will not build on or
  remove.
