Skip to content

Install with the CLI

mikroscope install puts the agent on the router over ssh. It checks the router, reads its architecture, lists every RouterOS command, asks, writes, and probes the agent. Pick where the image comes from:

Terminal window
mikroscope install --router admin@192.168.88.1 --remote-image jmrplens/mikroscope-agent:1.6.1

The router pulls the image for its own architecture from the registry. Nothing is built or uploaded, and /container/config is not touched. The same image is on GHCR as ghcr.io/jmrplens/mikroscope-agent:1.6.1.

A router that cannot reach a registry takes the published image tar: Offline install.

  • The router meets the Requirements: RouterOS 7.24 or later, the container package and device-mode container=yes. Run mikroscope doctor --router admin@192.168.88.1 with the same flags you will install with; it writes nothing and names the fix for anything missing.
  • ssh reaches the router as an admin user with a key or an ssh agent. For a router not yet in known_hosts, add --ssh-option StrictHostKeyChecking=accept-new.
  • Choose where the image comes from: Install methods compares the routes.
  1. Reads the router. install runs doctor’s checks in one connect and reads the router’s architecture there (--arch auto, the default). A MISSING check stops it with N prerequisite(s) missing; nothing was written; a WARN is printed and does not. --no-doctor skips the checks; the architecture then takes one connect of its own, or none with --remote-image, where the router picks it from the image’s index.

  2. Gets the image. With --remote-image nothing is fetched. With --agent-tar it checks that the tar is a mikroscope agent image for the router’s architecture and prints using mikroscope-agent-amd64.tar: linux/amd64, agent <size> KiB. With neither it builds one from the checkout.

  3. Prints the plan. One line of options (a token shows as token=(set), never its value), the tag, then every RouterOS command numbered, the install manifest first. The container step names the upload with its size, or the router pulls <image> (nothing is uploaded). It ends with nothing above has been written yet. mikroscope plan and install --dry-run stop here without connecting, and assume arm64 where the architecture matters.

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

  5. Checks every step at once. One connect asks, for each step, whether mikroscope’s object is there and whether something else already holds it. A step already this install’s prints ok … (already present) and is skipped. A step something else holds stops the install before the first write.

  6. Writes. The install manifest first, then each object, one connect per write:

    new install manifest mikroscope/mikroscope.manifest.txt
    new veth interface veth-mikroscope
    new router address 172.30.10.1
    new interface-list membership LAN
    new address-list membership LANs
    pull the router pulls registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1 itself
    new container mikroscope
    install done: 6 step(s) created

    On the tar route the container step uploads the tar (up uploading mikroscope.tar (<size> KiB)), waits for RouterOS to extract it, up to --extract-timeout (120 s by default), then deletes the tar and starts the container. If extraction is not done by then, it stops and leaves the tar, which uninstall removes.

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

    probing http://172.30.10.2:9123/healthz from this host …
    direct transport ok: agent 1.6.1 (<commit>) built <time>, 10 Hz, seq 7, 0 slipped, 2ms round trip

    When the probe fails, it asks the router whether the container runs before it suggests anything else: Network access.

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

What install writes to your router

  • the install manifest, a file mikroscope/<name>.manifest.txt on the install's disk that lists the options and every object below
  • a veth
  • one address
  • one interface-list membership, unless --iface-list none
  • one address-list entry, unless --addr-list none
  • an envlist
  • the image tar, deleted once the container is extracted, unless --remote-image has the router pull the image
  • the container, and its root mikroscope/<name> on the same disk

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.

Storage and container settings has where each object lives, what the envlist carries and the container’s settings. Firewall lists explains the two list memberships.

Every object install creates carries the comment mikroscope:<name> (managed by mikroscope), and the install manifest lists each one with its identity: the veth by name, the address by interface, a list membership by list and interface, an address-list entry by list and address. A removal matches the tag and that identity, never a pattern.

  • /container/envs and /file carry no comment. The envlist is signed by an entry MIKROSCOPE_TAG whose value is the exact tag, which the agent ignores, and the manifest by its tag= line.
  • Every find quotes its address and port values. Unquoted, RouterOS compares them as typed values and an unquoted dst-port=9123 matches nothing (verified).
  • RouterOS can report an error over ssh with exit status 0, and abandons the rest of a ;-joined line at the first one. So a write that prints anything counts as a failure. If the container step fails after an upload, the tar is taken back (undo removed the uploaded …).

Installer safeguards lists what else the installer refuses.

upgrade and uninstall are on Upgrade and uninstall.

Terminal window
mikroscope status --router admin@192.168.88.1

status reads the install’s shape from its manifest, or from its tagged objects for an install made without one, and prints the ownership count of every step from one connect:

install on the router (manifest mikroscope/mikroscope.manifest.txt): veth veth-mikroscope, subnet 172.30.10.0/30, lists LAN/LANs, port 9123, pulled from registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1
manifest mikroscope/mikroscope.manifest.txt: the plan comes from it
1 install manifest mikroscope/mikroscope.manifest.txt
1 veth interface veth-mikroscope
1 router address 172.30.10.1
1 interface-list membership LAN
1 address-list membership LANs
8 container mikroscope
agent: 1.6.1 (<commit>) built <time>, 10 Hz, seq 14 (oldest 1), up 1s, 0 slipped, 1ms round trip
  • When nothing is installed it ends with verified: nothing mikroscope created remains on the router and probes nothing.
  • Otherwise it probes /healthz with a 3 s timeout and prints the agent’s version, rate, sequence and oldest sequence, uptime, slipped ticks and round trip, then one line about the board: whether this build maps the kernel’s port names to RouterOS’s on it (Port names).
  • If the agent does not answer, it prints agent: not reachable from this host with the error, and still exits 0.
Terminal window
mikroscope doctor # read-only: prerequisites, then the running agent's ring
mikroscope plan # every command, nothing written
mikroscope install # checks, listing, confirmation, writes, probe
mikroscope status # the install's shape, ownership counts, agent health
mikroscope upgrade --remote-image … # a new image; the container step and anything missing
mikroscope uninstall --yes # removes everything the install created (without --yes: lists)
mikroscope image --arch arm64 # the image tar, for side-loading by hand
mikroscope plan --rsc --out install.rsc # the same install, as a RouterOS script

--router takes user@host or an ssh config alias, and every verb that connects needs it.

These flags read their default from a MIKROSCOPE_* variable: --router, --ssh-port, --ssh-key, --ssh-option, --name, --veth, --subnet, --iface-list, --addr-list, --disk, --arch, --token, --lan-address, --agent-tar and --remote-image. The file .env.example documents them. The CLI does not read .env itself:

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

status, upgrade and uninstall read the install’s shape from the router, so a shape flag you leave out takes the installed value. A flag or a variable that contradicts the install is refused, naming both values, before anything is written.

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 (an IPv4 /30 at its network address) and --triggers, which the agent’s own parser checks. A value out of bounds fails the verb with exit status 2 and writes nothing. The agent parses TRIGGERS again when it starts, since an envlist can be edited by hand on the router, and a value it rejects there makes it exit and log bad configuration. CLI has every flag, and Environment variables every variable.

Each ssh connect costs the router CPU for as long as it lasts, so the CLI batches every read into one connect: doctor is one, status is one, and install’s questions about every step are one. Each write takes one more, plus the scp upload on the tar route. ssh is never a data path: record and forward reach the agent over HTTP or the RouterOS API. SSH cost has the measurement.