# Four ways to install

The four routes the agent image can take to the router — a Go checkout, the published tar, a registry the router pulls from, or a RouterOS script you paste in — what each one needs, what each one writes, and how to verify a downloaded release.

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

The agent is a container image, and the four routes below differ in one thing
only: how that image reaches the router. Everything else `install` writes — the
veth, the address, the two list memberships, the envlist, the container and its
tag — is the same whichever route you take, and so are the prerequisites:
[What the router needs](/mikroscope/install/prerequisites/) comes first in all
four, because `device-mode container=yes` needs a hand on the device and no
route works around it.

## Which one

| Route                                            | Needs                                                            | Prefer it when                                                    |
| ------------------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------- |
| **[A registry pull](#a-registry-pull)** — start here | the router can reach Docker Hub; nothing else                 | almost always: one command, nothing to choose, nothing uploaded   |
| [A RouterOS script](#a-routeros-script)          | a terminal on the router; `--remote-image`                       | you reach the router through WinBox or WebFig and not over ssh    |
| [A checkout, with Go](#a-checkout-with-go)       | Go 1.27 and the repository; ssh to the router                    | you are working on mikroscope and want the agent from your tree   |
| [The published tar](#the-published-tar)          | the release assets, the right one for the board; ssh to the router | the router cannot reach a registry                               |

**Take the first one unless something stops you.** A registry pull is a single
command with nothing to pick: the published image index carries every platform
a MikroTik container can be, so the router matches its own and no one has to
know whether the board is 64-bit ARM or one of the two 32-bit kinds. Nothing
lands on the flash, and `uninstall` has no file to account for.

The tar is last on purpose. It is the right route for a router with no way out
to a registry, and it is the only one where **you** pick the architecture — the
way that goes wrong is an image that installs, starts and dies with
`exec format error` in the container log. If you take it, read
[which tar](#which-tar) before downloading.

## A registry pull

The recommended route, and the shortest. One command, nothing to download,
nothing uploaded:

```sh
mikroscope install --router user@192.168.88.1 \
  --remote-image jmrplens/mikroscope-agent:1.0.4
```

Nothing is uploaded, no tar lands on the device, and `uninstall` has no file to
account for: the container step becomes
`/container/add remote-image="jmrplens/mikroscope-agent:1.0.4" …` and the plan
prints `the router pulls … (nothing is uploaded)` where the upload line would
be. The release publishes the image twice, as
`jmrplens/mikroscope-agent:1.0.4` on Docker Hub and as
`ghcr.io/jmrplens/mikroscope-agent:1.0.4` on GHCR. Both carry `linux/amd64`,
`linux/arm64`, `linux/arm/v7` and `linux/arm/v5`, and RouterOS picks the one its
architecture needs — which is why this route asks nothing about the board: the
two kinds of 32-bit ARM MikroTik ships are both in the index.

The reference above is the Docker Hub one, and it carries no registry host: the
router pulls it from whatever `/container/config registry-url` already names,
and RouterOS ships that setting as `https://registry-1.docker.io`. On a router
where nobody has changed it, the command above needs nothing set first — on the
RB5009 this project is measured on, that setting reads
`https://registry-1.docker.io`. The GHCR reference is the alternative, and it
needs `/container/config/set registry-url=https://ghcr.io` on the device first,
which is a change to every container on it.

It needs two things the other routes do not: the router has to reach the
registry, and it has to have room in RAM for the layers while it extracts them.

> **The registry host is a global router setting**
>
> RouterOS takes the registry host from `/container/config registry-url`, which is global to the
> device and shared with every other container on it, and only the rest of the reference goes into
> `remote-image=`. mikroscope never writes that setting — pointing your router's registry somewhere
> else to install a probe would be a change to somebody else's containers. `doctor` reads it
> instead, and when the reference names a host the setting does not match it names the one command
> to run — `/container/config/set registry-url=https://ghcr.io` for the GHCR reference. Install
> with `--agent-tar` if you would rather not change it.

A reference with no host — `jmrplens/mikroscope-agent:1.0.4` — leaves the
registry to whatever the router is already configured for, and `doctor` then
checks nothing about it. `--remote-image` reads its default from
`MIKROSCOPE_REMOTE_IMAGE`, and `upgrade` takes it too; `image` refuses it,
because there is no tar to write.

## A RouterOS script

For a router you reach through WinBox or WebFig, or where you do not want ssh
from another machine at all:

```sh
mikroscope plan --rsc \
  --remote-image jmrplens/mikroscope-agent:1.0.4 \
  --out install.rsc
```

The file holds the same commands `install` runs, in the same order, each object
tagged the same way, so `status` and `uninstall` from the CLI recognise them
afterwards. Read it, then paste it into the router's terminal, or upload it and
`/import` it. Without `--out` it goes to standard output. It carries its own
header: the tag it writes, what to check before running it, and, at the end,
`/container/print where name~"mikroscope"` and the `/healthz` URL the agent
answers on.

Two caveats, both stated in the script itself:

- **It cannot upload anything.** A script running on the router has no way to
  put the image there, so pair it with `--remote-image`. Without one, the
  header says instead which filename to put on the device first — the name the
  container step expects — and how to regenerate the script for a registry
  pull.
- **With `--token`, the file is a credential.** The envlist line carries the
  token in clear, because the router needs it in clear. The CLI writes the file
  `0600`; what you do with it afterwards is the exposure.

Nothing in the script checks anything. There is no `doctor`, no question about
what the objects it creates would collide with, and no confirmation prompt: it
writes. Run `mikroscope doctor` from a machine that can, or read
[What the router needs](/mikroscope/install/prerequisites/) and check the three
prerequisites by hand, before you paste it.

All four routes were run end to end against the reference RB5009UG+S+
(RouterOS 7.24.2, arm64) on 2026-09-17, one after another, each installing
under its own name, veth and `/30` so that nothing already on the device was
touched, and each removed again before the next. In every one the agent
answered `/healthz` from the collector host: the checkout build and the
published `mikroscope-agent-arm64.tar` at a 2 ms round trip, the router's own
pull of `jmrplens/mikroscope-agent:1.0.4` from Docker Hub at 2 ms, and the
`plan --rsc` script — uploaded and `/import`ed, with no CLI involved in the
install itself — at 15 ms on its first samples. `uninstall` then verified by
ownership count in each case, and the router's `/export` after all four was
byte-identical to the one taken before them.

> **Untested**
>
> `--remote-image` against **GHCR**. `/container/config registry-url` is a single global RouterOS
> setting that mikroscope reads and never writes, and the reference router points at Docker Hub;
> pointing it at ghcr.io to test that path would change the registry for every other container on
> the device. The image is published to both registries and CI starts it from GHCR on all three
> architectures, but no router has pulled it from there. Nor has any route been run on arm or on
> x86_64 hardware.

## A checkout, with Go

```sh
git clone https://github.com/jmrplens/mikroscope
cd mikroscope
make build
bin/mikroscope install --router user@192.168.88.1
```

`plan`, `install`, `upgrade` and `image` build the agent themselves:
`go build ./cmd/mikroscope-agent` for `linux/<arch>` (`--arch`, default
`arm64`) with `CGO_ENABLED=0`, packed into an image tar without Docker. The
build path is relative, so run the CLI from the checkout. This is the only
route that installs an agent built from your own tree, which is why it is the
one to use while changing the agent.

`make build` leaves the CLI in `bin/mikroscope`. With no Go toolchain on
`PATH`, the verb stops before anything is written and names the other two
routes and the Go version it wanted.

## The published tar

The route for a router that cannot reach a registry. Two assets: the CLI
archive for the machine you run it from — which is
[Getting the CLI](/mikroscope/install/cli/), and has nothing to do with the
router — and one agent image tar, for the **router's** architecture. No Go
toolchain, no checkout.

### Which tar

| Your MikroTik                                  | `architecture-name` | Agent image tar                 |
| ----------------------------------------------- | ------------------- | ------------------------------- |
| RB5009, CCR2004, hAP ax³, other 64-bit ARM      | `arm64`             | `mikroscope-agent-arm64.tar`    |
| hEX Refresh / hEX S (2025), any EN7562CT board  | `arm`               | `mikroscope-agent-armv5.tar`    |
| Other 32-bit ARM (hAP ac², hAP ax², …)          | `arm`               | `mikroscope-agent-armv7.tar`, or the v5 one |
| CHR, x86 RouterOS                               | `x86_64`            | `mikroscope-agent-amd64.tar`    |

`mikroscope doctor --router …` prints the architecture off the device, so run
it first and let it tell you. **If you are not sure which 32-bit ARM board you
have, take the v5 tar**: MikroTik's container documentation says EN7562CT
boards "support only arm32v5 container images", and an ARMv5 image runs on
every 32-bit ARM MikroTik ships, while an ARMv7 one does not run on those.

1. Download `mikroscope_1.0.4_<os>_<arch>.tar.gz` (`.zip` on Windows) and the
   agent image tar from the table above, together with `checksums.txt` and
   `checksums.txt.sigstore.json`.

2. Verify them, below, before unpacking anything.

3. Unpack the CLI and install:

   ```sh
   tar xzf mikroscope_1.0.4_linux_x86_64.tar.gz
   ./mikroscope install --router user@192.168.88.1 \
     --arch arm64 --agent-tar mikroscope-agent-arm64.tar
   ```

The CLI reads the tar before it uploads it, which is what catches the wrong
download: it prints what it read, as
`using mikroscope-agent-arm64.tar: linux/arm64, agent <size> KiB`, and on the
ARMv7 image it adds the note that an EN7562CT board needs the v5 one instead.

It wants a one-image
`manifest.json`, the config that manifest names, one layer, and
`/mikroscope-agent` as the entrypoint; anything else fails as
`this is not a mikroscope agent image`. Then the image's architecture has to be
the one `--arch` says, or the verb fails naming the asset to download instead —
an `amd64` image on an arm64 board would otherwise install, start, and die with
`exec format error` in the container log. On a tar it accepts it prints what it
read, as `using mikroscope-agent-arm64.tar: linux/arm64, agent <size> KiB`.

`--agent-tar` reads its default from `MIKROSCOPE_AGENT_TAR`, and `upgrade` and
`image` take it too. From here on the install is the upload route: the tar goes
up with `scp`, RouterOS extracts it at add time, and `install` deletes it.

> **Two assets have similar names**
>
> `mikroscope-agent-arm64.tar` is the side-loadable container image, the one `--agent-tar` wants.
> `mikroscope-agent_1.0.4_linux_arm64.tar.gz` is an archive of the bare agent binary, for reading or
> running it outside a container; `--agent-tar` rejects it.

### Verifying the download

`checksums.txt` covers every archive and every agent image tar, and it is the
one file the signature vouches for. The signature is keyless: the identity is
the workflow run that produced it, recorded in a public transparency log, so
there is no key to fetch.

```sh
cosign verify-blob \
  --certificate-identity-regexp 'https://github.com/jmrplens/mikroscope/.github/workflows/release.yml@refs/tags/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --bundle checksums.txt.sigstore.json \
  checksums.txt
sha256sum --ignore-missing -c checksums.txt
```

`--ignore-missing` is what lets you check the two files you downloaded against
a list that covers the whole release. Each archive also ships an SPDX SBOM
(`<archive>.spdx.json`) with a signature bundle of its own, verified the same
way.

## See also

- [What the router needs](/mikroscope/install/prerequisites/): the three prerequisites every route
  shares, and what `doctor` checks.
- [Installing the agent](/mikroscope/install/): what `install` does once the image is decided.
- [Where things go](/mikroscope/install/layout/): the disk the tar and the container root use, and
  what `--remote-image` leaves off it.
- [Reaching the agent](/mikroscope/install/reaching-the-agent/): the probe that runs afterwards.
