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.
Before you start
Section titled “Before you start”- The router runs RouterOS 7.24 or later, with the
containerpackage anddevice-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).
Settings
Section titled “Settings”The form needs JavaScript. The script below is the default one: the router pulls the agent from Docker Hub.
A RAM disk is lost on reboot, so start on boot turns off.
Create the RAM disk first; uninstall leaves it: /disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs
Names the envlist, the tag and the root. A second install needs its own.
A name no interface on the router has.
A /30 at its network address: router 172.30.10.1, agent 172.30.10.2.
The agent's HTTP port, 1–65535.
The list a drop rule's in-interface-list=! names. none joins no list.
The list a drop rule's src-address-list=! names. none joins no list.
1–100.
How long the collector can be away without losing samples, 10–3600.
memory-max, like 64M.
Empty: derived from the ring, 16 MiB now. Else 8–1024.
0 turns triggered capture off. 0–256.
0 keeps each source's own floor. 0–1000.
The agent default is softnet-drop, oom, kmsg<=3, reset, irq-err and flash-bad.
Off, the agent cannot read kmsg, slabinfo, pagetypeinfo or the flash (MTD) counters.
32 random characters made in this page; nothing is sent or stored. Every endpoint but /healthz then asks for it.
Adds a dst-nat rule and a forward accept rule. Needs a token.
An IPv4 address the router has on the LAN.
Advanced
Restarts after a failure, 0–100.
Like 10s, 1m or 1h.
Whether RouterOS starts the container after a reboot.
Empty: RouterOS names it.
Tar only: how long the script waits for the extraction before it deletes the tar, 10s to 600s.
Install script
Section titled “Install script”This script contains the token in clear: treat it as a credential.
# 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.1The command reads the token from MIKROSCOPE_TOKEN: export it first.
What install writes to your router
- the install manifest, a file
mikroscope/on the install's disk that lists the options and every object below<name>. manifest.txt - 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-imagehas 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.
Upload the image tar
Section titled “Upload the image tar”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:
Fix the settings in the form to see these commands.
A registry pull uploads nothing: skip this step.
curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v1.6.1/mikroscope-agent-arm64.tar
curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v1.6.1/checksums.txt
sha256sum --ignore-missing -c checksums.txt
scp mikroscope-agent-arm64.tar admin@192.168.88.1:mikroscope.tarWithout 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.
Run the script
Section titled “Run the script”-
Open a terminal on the router: Terminal in WebFig, New Terminal in Winbox, or
ssh admin@192.168.88.1. -
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. -
Paste the script and press Enter. It waits up to 120 s for the container to start and prints
mikroscope: agent running, http://…/healthz, ormikroscope: not running yetand 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.
Verify
Section titled “Verify”Fix the settings in the form to see these commands.
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/healthzThe router’s fetch prints the agent’s /healthz answer. /healthz needs no
token.
Remove
Section titled “Remove”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.
Fix the settings in the form to see these commands.
With the CLI
mikroscope uninstall --router admin@192.168.88.1 --remote-image jmrplens/mikroscope-agent:1.6.1Uninstall 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.