Skip to content

Script generator

Install the agent without the CLI: pick the options, copy the script and paste it into the router’s terminal. The script is the one mikroscope plan --rsc prints for the same options, byte for byte. Everything happens in this page: nothing you type or generate is sent anywhere or stored.

  • The router runs RouterOS 7.24 or later, with the container package and device-mode container=yes (Requirements). The script checks the three first and stops before it writes anything when one is missing.
  • For a registry pull, the router reaches the registry. With no way out, choose Uploaded image tar.
  • A firewall that drops what is not in a list needs the agent in that list: Firewall lists.
  • On a CHR with the free licence, the router sends at most 1 Mbit/s, which a rate of 50 Hz reaches (Tested on).

The form needs JavaScript. The script below is the default one: the router pulls the agent from Docker Hub.

# mikroscope 1.6.1: install script for RouterOS 7.24 or later. Container name: mikroscope
# Every object it creates carries the comment "mikroscope:mikroscope (managed by mikroscope)", which is how
# `mikroscope status` and `uninstall` recognize them later. It lists them in
# mikroscope/mikroscope.manifest.txt on the router, which `mikroscope uninstall` reads and deletes last.
#
# The router pulls registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1 itself.
# The registry host is part of remote-image= (RouterOS 7.18 and later take it
# there), so this script neither reads nor changes the device-wide registry-url.
# A registry username set on the device for a registry other than registry-1.docker.io
# can make the pull end in `auth error`; `mikroscope doctor` warns about it.
#
# It runs as one block: a check that fails stops it before anything is written,
# and a step that fails stops the steps after it.

{
:if (!([/system/resource/get version] ~ "^(7[.](2[4-9]|[3-9][0-9]|[1-9][0-9][0-9])|([89]|[1-9][0-9]+)[.])")) do={ :error "mikroscope: needs RouterOS 7.24 or later" }
:if ([:len [/system/package/find name="container" disabled=no]] = 0) do={ :error "mikroscope: the container package is not installed" }
:local dm [:tostr [/system/device-mode/get container]]; :if ($dm != "yes" && $dm != "true") do={ :error "mikroscope: device-mode container is not enabled" }
:if ([:len [/interface/veth/find name="veth-mikroscope"]] > 0 && [:len [/interface/veth/find name="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"]] = 0) do={ :error "mikroscope: veth veth-mikroscope exists and is not mikroscope's" }
:if ([:len [/container/envs/find list="mikroscope-env"]] > 0 && [:len [/container/envs/find list="mikroscope-env" key="MIKROSCOPE_TAG" value="mikroscope:mikroscope (managed by mikroscope)"]] = 0) do={ :error "mikroscope: envlist mikroscope-env exists and is not mikroscope's" }
:if ([:len [/interface/list/find name="LAN"]] = 0) do={ :error "mikroscope: interface list LAN does not exist" }
:if ([:len [/file/find name="mikroscope/mikroscope.manifest.txt"]] > 0) do={ :if (!([:typeof [:find [/file/get [find name="mikroscope/mikroscope.manifest.txt"] contents] "\ntag=mikroscope:mikroscope (managed by mikroscope)\n"]] = "num")) do={ :error "mikroscope: mikroscope/mikroscope.manifest.txt exists and is not this install's manifest" } }
# install manifest mikroscope/mikroscope.manifest.txt
:local m "mikroscope-manifest=1\nname=mikroscope\ntag=mikroscope:mikroscope (managed by mikroscope)\ndisk=\nveth=veth-mikroscope\nsubnet=172.30.10.0/30\nport=9123\niface-list=LAN\naddr-list=LANs\nexpose=\ncontainer-name=\nremote-image=registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1\ntoken=no\ndir=mikroscope\nfile=mikroscope/mikroscope.manifest.txt\nobject=/interface/veth name=veth-mikroscope\nobject=/ip/address interface=veth-mikroscope\nobject=/interface/list/member interface=veth-mikroscope list=LAN\nobject=/ip/firewall/address-list list=LANs address=172.30.10.0/30\nobject=/container/envs list=mikroscope-env\nobject=/container interface=veth-mikroscope\ndir=mikroscope/mikroscope\n"; :if ([:len [/file/find name="mikroscope/mikroscope.manifest.txt"]] > 0) do={ /file/set [find name="mikroscope/mikroscope.manifest.txt"] contents=$m } else={ /file/add name="mikroscope/mikroscope.manifest.txt" contents=$m }
# veth interface veth-mikroscope
/interface/veth/add name="veth-mikroscope" address=172.30.10.2/30 gateway=172.30.10.1 comment="mikroscope:mikroscope (managed by mikroscope)"
# router address 172.30.10.1
/ip/address/add address=172.30.10.1/30 interface="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"
# interface-list membership LAN
/interface/list/member/add list="LAN" interface="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"
# address-list membership LANs
/ip/firewall/address-list/add list="LANs" address=172.30.10.0/30 comment="mikroscope:mikroscope (managed by mikroscope)"
# container mikroscope
:if ([:len [/container/envs/find list="mikroscope-env" key="MIKROSCOPE_TAG" value="mikroscope:mikroscope (managed by mikroscope)"]] > 0) do={ /container/envs/remove [find list="mikroscope-env"] }; /container/envs/add list="mikroscope-env" key=MIKROSCOPE_TAG value="mikroscope:mikroscope (managed by mikroscope)"; /container/envs/add list="mikroscope-env" key=RATE_HZ value="10"; /container/envs/add list="mikroscope-env" key=BUFFER_S value="60"; /container/envs/add list="mikroscope-env" key=PORT value="9123"; /container/envs/add list="mikroscope-env" key=ADDR value="172.30.10.2"; /container/envs/add list="mikroscope-env" key=MEM_LIMIT_MB value="16"; /container/envs/add list="mikroscope-env" key=CAPTURE_MB value="4"; /container/add remote-image="registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1" interface="veth-mikroscope" root-dir=mikroscope/mikroscope envlist="mikroscope-env" logging=yes start-on-boot=yes restart-policy=on-failure restart-max-count=5 restart-interval=10s memory-max=64M privileged=yes ignore-remote-image-change=yes comment="mikroscope:mikroscope (managed by mikroscope)"; /container/start [find comment="mikroscope:mikroscope (managed by mikroscope)"]
:local k 0; :while ([:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)" running]] = 0 && $k < 120) do={ :delay 1s; :set k ($k + 1) }
:if ([:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)" running]] > 0) do={ :put "mikroscope: agent running, http://172.30.10.2:9123/healthz" } else={ :put "mikroscope: not running yet; see /log/print where topics~\"container\"" }
}
# When it is done: /container/print where comment="mikroscope:mikroscope (managed by mikroscope)"
# The agent answers on http://172.30.10.2:9123/healthz from the router's LAN.

The same script from the CLI

mikroscope plan --rsc --remote-image jmrplens/mikroscope-agent:1.6.1

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.

Only for Uploaded image tar. Download the tar for the router’s architecture, check it against the release’s checksums and copy it to the router under the name the script expects:

A registry pull uploads nothing: skip this step.

Without scp, upload the file in WebFig or Winbox under Files and rename it to that name. The script deletes the tar once RouterOS has extracted it.

  1. Open a terminal on the router: Terminal in WebFig, New Terminal in Winbox, or ssh admin@192.168.88.1.

  2. Wait for the ] > prompt. On the first login RouterOS asks whether to show the software licence: answer that first. A script pasted into the licence question loses its first lines.

  3. Paste the script and press Enter. It waits up to 120 s for the container to start and prints mikroscope: agent running, http://…/healthz, or mikroscope: not running yet and where to look.

Or download the .rsc, upload it under Files and run /import file-name=mikroscope-install.rsc.

The script runs as one block. A check that fails stops it before anything is written, with a line that starts mikroscope: and names what is missing. A step that fails stops the steps after it; what it already created carries the tag, and the uninstall script or mikroscope uninstall removes it.

On the router

/container/print where comment="mikroscope:mikroscope (managed by mikroscope)"
:put ([/tool/fetch url="http://172.30.10.2:9123/healthz" output=user as-value]->"data")

From a computer on the LAN

curl http://172.30.10.2:9123/healthz

The router’s fetch prints the agent’s /healthz answer. /healthz needs no token.

Remove the install with the CLI, which reads the manifest the script wrote and removes every object in it, or paste the uninstall script, which sends the same commands in the same order. Generate it with the settings you installed with.

With the CLI

mikroscope uninstall --router admin@192.168.88.1 --remote-image jmrplens/mikroscope-agent:1.6.1

Uninstall script

# mikroscope 1.6.1: uninstall script for RouterOS 7.24 or later. Container name: mikroscope
# It removes what the install script with the same settings created: every
# object tagged "mikroscope:mikroscope (managed by mikroscope)", the container root mikroscope/mikroscope,
# and mikroscope/mikroscope.manifest.txt last. It sends the commands `mikroscope uninstall` sends,
# in its order, and selects nothing by pattern.

{
# container mikroscope
:do { :local had [:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :do { /container/stop [find comment="mikroscope:mikroscope (managed by mikroscope)"] } on-error={}; :local s 0; :while (([:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)" running]] + [:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)" stopping]]) > 0 && $s < 30) do={ :delay 1s; :set s ($s + 1) }; /container/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :local i 0; :while ([:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)"]] > 0 && $i < 20) do={ :delay 1s; :set i ($i + 1) }; :if ($had > 0) do={ :local r 0; :while ([:len [/file/find name="mikroscope/mikroscope"]] > 0 && ([:len [/container/find root-dir="/mikroscope/mikroscope"]] + [:len [/container/find root-dir="mikroscope/mikroscope"]]) = 0 && $r < 10) do={ :delay 1s; :set r ($r + 1) }; :if ([:len [/file/find name="mikroscope/mikroscope"]] > 0 && ([:len [/container/find root-dir="/mikroscope/mikroscope"]] + [:len [/container/find root-dir="mikroscope/mikroscope"]]) = 0) do={ :do { /file/remove [find name="mikroscope/mikroscope"] } on-error={} } }; :if ([:len [/container/envs/find list="mikroscope-env" key="MIKROSCOPE_TAG" value="mikroscope:mikroscope (managed by mikroscope)"]] > 0) do={ /container/envs/remove [find list="mikroscope-env" key!="MIKROSCOPE_TAG"]; /container/envs/remove [/container/envs/find list="mikroscope-env" key="MIKROSCOPE_TAG" value="mikroscope:mikroscope (managed by mikroscope)"] } } on-error={ :put "mikroscope: could not remove container mikroscope" }
# address-list membership LANs
:do { /ip/firewall/address-list/remove [find list="LANs" address="172.30.10.0/30" comment="mikroscope:mikroscope (managed by mikroscope)"] } on-error={ :put "mikroscope: could not remove address-list membership LANs" }
# interface-list membership LAN
:do { /interface/list/member/remove [find interface="veth-mikroscope" list="LAN" comment="mikroscope:mikroscope (managed by mikroscope)"] } on-error={ :put "mikroscope: could not remove interface-list membership LAN" }
# router address 172.30.10.1
:do { /ip/address/remove [find interface="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"] } on-error={ :put "mikroscope: could not remove router address 172.30.10.1" }
# veth interface veth-mikroscope
:do { /interface/veth/remove [find name="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"] } on-error={ :put "mikroscope: could not remove veth interface veth-mikroscope" }
:do { :local n [:len [/ip/firewall/address-list/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/firewall/address-list/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/firewall/address-list " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/firewall/address-list" }
:do { :local n [:len [/interface/list/member/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /interface/list/member/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/interface/list/member " . $n) } } on-error={ :put "mikroscope: could not sweep /interface/list/member" }
:do { :local n [:len [/ip/firewall/nat/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/firewall/nat/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/firewall/nat " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/firewall/nat" }
:do { :local n [:len [/ip/firewall/filter/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/firewall/filter/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/firewall/filter " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/firewall/filter" }
:do { :local n [:len [/ip/firewall/raw/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/firewall/raw/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/firewall/raw " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/firewall/raw" }
:do { :local n [:len [/ip/firewall/mangle/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/firewall/mangle/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/firewall/mangle " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/firewall/mangle" }
:do { :local n [:len [/ip/route/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/route/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/route " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/route" }
:do { :local n [:len [/container/mounts/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /container/mounts/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/container/mounts " . $n) } } on-error={ :put "mikroscope: could not sweep /container/mounts" }
:do { :local n [:len [/ip/address/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /ip/address/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/ip/address " . $n) } } on-error={ :put "mikroscope: could not sweep /ip/address" }
:do { :local n [:len [/interface/veth/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /interface/veth/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/interface/veth " . $n) } } on-error={ :put "mikroscope: could not sweep /interface/veth" }
:do { :local n [:len [/interface/list/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /interface/list/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/interface/list " . $n) } } on-error={ :put "mikroscope: could not sweep /interface/list" }
:do { :local n [:len [/disk/find comment="mikroscope:mikroscope (managed by mikroscope)"]]; :if ($n > 0) do={ /disk/remove [find comment="mikroscope:mikroscope (managed by mikroscope)"]; :put ("@@swept=/disk " . $n) } } on-error={ :put "mikroscope: could not sweep /disk" }
# install manifest mikroscope/mikroscope.manifest.txt
:if (([:len [/ip/firewall/address-list/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/interface/list/member/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/firewall/nat/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/firewall/filter/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/firewall/raw/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/firewall/mangle/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/route/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/container/mounts/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/ip/address/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/interface/veth/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/interface/list/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/disk/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/container/find comment="mikroscope:mikroscope (managed by mikroscope)"]] + [:len [/container/envs/find list="mikroscope-env" key="MIKROSCOPE_TAG" value="mikroscope:mikroscope (managed by mikroscope)"]]) > 0) do={ :error "mikroscope: objects tagged mikroscope:mikroscope (managed by mikroscope) remain, so mikroscope/mikroscope.manifest.txt stays" }; :if ([:len [/file/find name="mikroscope/mikroscope.manifest.txt"]] > 0) do={ :if ([:typeof [:find [/file/get [find name="mikroscope/mikroscope.manifest.txt"] contents] "\ntag=mikroscope:mikroscope (managed by mikroscope)\n"]] = "num") do={ :if ([:len [/file/find name="mikroscope/mikroscope"]] > 0 && ([:len [/container/find root-dir="/mikroscope/mikroscope"]] + [:len [/container/find root-dir="mikroscope/mikroscope"]]) = 0) do={ :do { /file/remove [find name="mikroscope/mikroscope"] } on-error={} }; /file/remove [find name="mikroscope/mikroscope.manifest.txt"] } }; :if ([:len [/file/find name~"^mikroscope/"]] = 0) do={ /file/remove [find name="mikroscope" type="directory"] }
:put "mikroscope: removed; /container/print where comment=\"mikroscope:mikroscope (managed by mikroscope)\" lists nothing"
}

Both remove the manifest last, then the mikroscope directory once it is empty. While anything tagged remains, the manifest stays, so the next attempt still finds the install. Neither touches what the install did not create: device-mode, the container package, /container/config, or a RAM disk you added.