Manual install: terminal
Install the agent one RouterOS command at a time, in the router’s terminal: for a router with no
computer that runs the CLI, or to see and adapt every object. The commands are the ones install
runs, with placeholders in capitals. For the same commands with your values filled in, use the
Script generator.
Before you start
Section titled “Before you start”Pick a value for each placeholder. The defaults are the ones install uses:
| Placeholder | Default | Meaning |
|---|---|---|
NAME |
mikroscope |
the install’s name |
TAG |
mikroscope:NAME (managed by mikroscope) |
the comment on every object; keep this form so the CLI recognises the install |
VETH |
veth-mikroscope |
the veth’s name |
NET/30 |
172.30.10.0/30 |
an IPv4 /30 at its network address, unused on the router |
GW_IP |
172.30.10.1 |
the router’s end of the /30: the network address + 1 |
AGENT_IP |
172.30.10.2 |
the agent’s end of the /30: the network address + 2 |
PORT |
9123 |
the agent’s HTTP port |
IFACE_LIST |
LAN |
the interface list the veth joins; none in the manifest when you skip the membership |
ADDR_LIST |
LANs |
the address list the /30 joins; none in the manifest when you skip the membership |
ENVLIST |
NAME-env |
the container’s envlist |
DISK |
empty (internal flash) | a disk slot, such as tmpfs or usb1; it prefixes the three paths below |
MANIFEST_DIR |
mikroscope |
DISK/mikroscope on a disk |
MANIFEST_FILE |
mikroscope/ |
the install manifest |
ROOT_DIR |
mikroscope/NAME |
the container’s root |
IMAGE_REF |
registry-1. |
the image the router pulls (registry pull) |
IMAGE_FILE |
NAME.tar |
the uploaded image tar (tar route); DISK/NAME.tar on a disk |
RATE_HZ |
10 |
the sampler rate, 1–100 Hz |
BUFFER_S |
60 |
the ring, 10–3600 s |
MEM_LIMIT_MB |
16 |
the agent’s Go memory limit, below |
CAPTURE_MB |
4 |
the triggered-capture budget, 0–256 MiB; 0 turns captures off |
MEMORY_MAX |
64M |
the container’s memory limit |
START_ON_BOOT |
yes |
no for a root on a RAM disk, which a reboot empties |
PRIVILEGED |
yes |
no loses the kernel log, the slab counts, the flash counters and the PMU |
RESTART_MAX_COUNT |
5 |
restarts after a failure |
RESTART_INTERVAL |
10s |
the wait between them |
EXTRACT_TIMEOUT_S |
120 |
how long to wait for RouterOS to extract the tar (tar route) |
FLOOR_HZ |
none | optional: one cadence for every level source |
TRIGGERS |
none | optional: trigger conditions (Triggered capture) |
TOKEN |
none | optional: the bearer token the agent asks for; a secret |
CONTAINER_NAME |
empty (RouterOS names it) | optional: the container’s name= |
LAN_IP |
none | with --expose only: the router’s LAN address |
Scroll sideways to see every column
An empty default stays empty: on internal flash the manifest’s line is disk=, and without a
container name container-name=, with nothing after the =.
MEM_LIMIT_MB is RATE_HZ × BUFFER_S × the size a sample takes in the ring × 2.5, rounded up,
at least 16 and at most ¾ of MEMORY_MAX while that still leaves room for the ring. With a
MEMORY_MAX of 64M that gives 16 at 10 Hz and 60 s, 25 at 50 Hz and 60 s, and 48 at 100 Hz and
60 s. The generator computes it for you.
Check the router. Read the release and the architecture, the container package and device
mode:
/system/resource/print/system/package/print where name="container"/system/device-mode/printversion must be 7.24 or later and architecture-name one of arm64, arm or x86_64; the
package must be listed and not disabled; device mode must show container: yes.
Requirements has the fixes: the device-mode step needs a
press of the reset or mode button, or a power cut, within 5 minutes; on CHR, power the virtual
machine off and on.
Check for collisions. Each of these must print nothing, or a count of 0:
/interface/veth/print where name="VETH"/container/envs/print count-only where list="ENVLIST"/ip/address/print where address in NET/30/ip/route/print where dst-address in NET/30/file/print where name="MANIFEST_FILE"And these must find what you name: the interface list, and the disk if you use one.
/interface/list/print where name="IFACE_LIST"/disk/printFor a root on a RAM disk, create one first: /disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs,
then use DISK tmpfs and START_ON_BOOT no. Whether the firewall drops the agent’s replies
is on Firewall lists.
Write the install manifest
Section titled “Write the install manifest”The manifest lists every object the next steps create, so that mikroscope uninstall and the
removal below find them all. Leave out the object= line of any step you skip; for a list
membership you skip, also write none after its iface-list= or addr-list=.
:local m "mikroscope-manifest=1\nname=NAME\ntag=TAG\ndisk=DISK\nveth=VETH\nsubnet=NET/30\nport=PORT\niface-list=IFACE_LIST\naddr-list=ADDR_LIST\nexpose=\ncontainer-name=CONTAINER_NAME\nremote-image=IMAGE_REF\ntoken=no\ndir=MANIFEST_DIR\nfile=MANIFEST_FILE\nobject=/interface/veth name=VETH\nobject=/ip/address interface=VETH\nobject=/interface/list/member interface=VETH list=IFACE_LIST\nobject=/ip/firewall/address-list list=ADDR_LIST address=NET/30\nobject=/container/envs list=ENVLIST\nobject=/container interface=VETH\ndir=ROOT_DIR\n"; :if ([:len [/file/find name="MANIFEST_FILE"]] > 0) do={ /file/set [find name="MANIFEST_FILE"] contents=$m } else={ /file/add name="MANIFEST_FILE" contents=$m }:local m "mikroscope-manifest=1\nname=NAME\ntag=TAG\ndisk=DISK\nveth=VETH\nsubnet=NET/30\nport=PORT\niface-list=IFACE_LIST\naddr-list=ADDR_LIST\nexpose=\ncontainer-name=CONTAINER_NAME\nremote-image=\ntoken=no\ndir=MANIFEST_DIR\nfile=MANIFEST_FILE\nobject=/interface/veth name=VETH\nobject=/ip/address interface=VETH\nobject=/interface/list/member interface=VETH list=IFACE_LIST\nobject=/ip/firewall/address-list list=ADDR_LIST address=NET/30\nfile=IMAGE_FILE\nobject=/container/envs list=ENVLIST\nobject=/container interface=VETH\ndir=ROOT_DIR\n"; :if ([:len [/file/find name="MANIFEST_FILE"]] > 0) do={ /file/set [find name="MANIFEST_FILE"] contents=$m } else={ /file/add name="MANIFEST_FILE" contents=$m }The tar’s manifest leaves remote-image= empty and names the tar in a file= line, so that
uninstall also removes a tar an extraction left behind.
Read it back with :put [/file/get [find name="MANIFEST_.
RouterOS script explains each line.
Create the veth
Section titled “Create the veth”/interface/veth/add name="VETH" address=AGENT_IP/30 gateway=GW_IP comment="TAG"Add the address
Section titled “Add the address”/ip/address/add address=GW_IP/30 interface="VETH" comment="TAG"Add list memberships
Section titled “Add list memberships”Optional. They let the agent’s replies past raw firewall rules that drop by interface list or
address list; skip either when no rule of yours needs it, and drop its object= line from the
manifest and write none after its iface-list= or addr-list=.
A membership is not scoped to mikroscope: any other rule that matches the list matches the veth or
the /30 too (Firewall lists).
/interface/list/member/add list="IFACE_LIST" interface="VETH" comment="TAG"/ip/firewall/address-list/add list="ADDR_LIST" address=NET/30 comment="TAG"Create the envlist
Section titled “Create the envlist”/container/envs/add list="ENVLIST" key=MIKROSCOPE_TAG value="TAG"/container/envs/add list="ENVLIST" key=RATE_HZ value="RATE_HZ"/container/envs/add list="ENVLIST" key=BUFFER_S value="BUFFER_S"/container/envs/add list="ENVLIST" key=PORT value="PORT"/container/envs/add list="ENVLIST" key=ADDR value="AGENT_IP"/container/envs/add list="ENVLIST" key=MEM_LIMIT_MB value="MEM_LIMIT_MB"/container/envs/add list="ENVLIST" key=CAPTURE_MB value="CAPTURE_MB"Add the optional entries you use:
/container/envs/add list="ENVLIST" key=FLOOR_HZ value="FLOOR_HZ"/container/envs/add list="ENVLIST" key=TRIGGERS value="TRIGGERS"/container/envs/add list="ENVLIST" key=TOKEN value="TOKEN"The TOKEN value is stored in clear: treat it as readable by any RouterOS user with read, and
keep other credentials out of the envlist.
Create the container
Section titled “Create the container”/container/add remote-image="IMAGE_REF" interface="VETH" root-dir=ROOT_DIR envlist="ENVLIST" logging=yes start-on-boot=START_ON_BOOT restart-policy=on-failure restart-max-count=RESTART_MAX_COUNT restart-interval=RESTART_INTERVAL memory-max=MEMORY_MAX privileged=PRIVILEGED ignore-remote-image-change=yes comment="TAG"RouterOS pulls the image for its own architecture. The router needs to reach the registry and to have room in RAM for the layers.
-
Upload the tar for the router’s architecture as
IMAGE_FILE, in Files (WebFig or Winbox) or withscp(Offline install). -
Create the container from it:
/container/add file=IMAGE_FILE interface="VETH" root-dir=ROOT_DIR envlist="ENVLIST" logging=yes start-on-boot=START_ON_BOOT restart-policy=on-failure restart-max-count=RESTART_MAX_COUNT restart-interval=RESTART_INTERVAL memory-max=MEMORY_MAX privileged=PRIVILEGED ignore-remote-image-change=yes comment="TAG" -
Wait for RouterOS to extract it, then delete the tar. The line stops, and keeps the tar, if extraction is not done within
EXTRACT_TIMEOUT_S::local w 0; :while ([:len [/container/find comment="TAG" stopped]] = 0 && $w < EXTRACT_TIMEOUT_S) do={ :delay 1s; :set w ($w + 1) }; :if ([:len [/container/find comment="TAG" stopped]] = 0) do={ :error "mikroscope: the image was not extracted within EXTRACT_TIMEOUT_S s; IMAGE_FILE stays" }; /file/remove [find name="IMAGE_FILE"]
Expose on the LAN
Section titled “Expose on the LAN”Optional: to reach the agent on the router’s LAN address, add the two rules --expose writes
(Expose on the LAN). They need a TOKEN in the envlist.
Write the manifest again first, so that it records them: expose=LAN_IP, token=yes and one
object= line per rule.
:local m "mikroscope-manifest=1\nname=NAME\ntag=TAG\ndisk=DISK\nveth=VETH\nsubnet=NET/30\nport=PORT\niface-list=IFACE_LIST\naddr-list=ADDR_LIST\nexpose=LAN_IP\ncontainer-name=CONTAINER_NAME\nremote-image=IMAGE_REF\ntoken=yes\ndir=MANIFEST_DIR\nfile=MANIFEST_FILE\nobject=/interface/veth name=VETH\nobject=/ip/address interface=VETH\nobject=/interface/list/member interface=VETH list=IFACE_LIST\nobject=/ip/firewall/address-list list=ADDR_LIST address=NET/30\nobject=/ip/firewall/nat chain=dstnat dst-address=LAN_IP dst-port=PORT protocol=tcp\nobject=/ip/firewall/filter chain=forward dst-address=AGENT_IP dst-port=PORT protocol=tcp\nobject=/container/envs list=ENVLIST\nobject=/container interface=VETH\ndir=ROOT_DIR\n"; :if ([:len [/file/find name="MANIFEST_FILE"]] > 0) do={ /file/set [find name="MANIFEST_FILE"] contents=$m } else={ /file/add name="MANIFEST_FILE" contents=$m }:local m "mikroscope-manifest=1\nname=NAME\ntag=TAG\ndisk=DISK\nveth=VETH\nsubnet=NET/30\nport=PORT\niface-list=IFACE_LIST\naddr-list=ADDR_LIST\nexpose=LAN_IP\ncontainer-name=CONTAINER_NAME\nremote-image=\ntoken=yes\ndir=MANIFEST_DIR\nfile=MANIFEST_FILE\nobject=/interface/veth name=VETH\nobject=/ip/address interface=VETH\nobject=/interface/list/member interface=VETH list=IFACE_LIST\nobject=/ip/firewall/address-list list=ADDR_LIST address=NET/30\nobject=/ip/firewall/nat chain=dstnat dst-address=LAN_IP dst-port=PORT protocol=tcp\nobject=/ip/firewall/filter chain=forward dst-address=AGENT_IP dst-port=PORT protocol=tcp\nfile=IMAGE_FILE\nobject=/container/envs list=ENVLIST\nobject=/container interface=VETH\ndir=ROOT_DIR\n"; :if ([:len [/file/find name="MANIFEST_FILE"]] > 0) do={ /file/set [find name="MANIFEST_FILE"] contents=$m } else={ /file/add name="MANIFEST_FILE" contents=$m }Then add the two rules:
/ip/firewall/nat/add chain=dstnat dst-address=LAN_IP protocol=tcp dst-port=PORT action=dst-nat to-addresses=AGENT_IP to-ports=PORT comment="TAG":local d [/ip/firewall/filter/find chain=forward action=drop]; :if ([:len $d] > 0) do={ /ip/firewall/filter/add chain=forward dst-address=AGENT_IP protocol=tcp dst-port=PORT connection-nat-state=dstnat action=accept comment="TAG" place-before=($d->0) } else={ /ip/firewall/filter/add chain=forward dst-address=AGENT_IP protocol=tcp dst-port=PORT connection-nat-state=dstnat action=accept comment="TAG" }Start and verify
Section titled “Start and verify”/container/start [find comment="TAG"]On the router:
/container/print where comment="TAG"/log/print where topics~"container":put ([/tool/fetch url="http://AGENT_IP:PORT/healthz" output=user as-value]->"data")After a registry pull, RouterOS downloads and extracts the image before it starts the container.
Until the log shows *** download/extract done, the container shows the E
(DOWNLOADING/EXTRACTING) flag and the last command prints failure: Invalid argument: run the
three again.
Then the container shows the R (running) flag. The log ends with *** started /mikroscope-agent and
the agent’s own line, mikroscope-agent <version> …: kernel …, 10 Hz, ring 60 s, listening on AGENT_IP:PORT, …. The last command prints {"ok":true,…}. From a computer on the router’s LAN,
curl http:// answers the same, and mikroscope status recognises the
install.
Remove
Section titled “Remove”Run the removals, newest first. The first stops and removes the container and its root and
envlist; the last removes the manifest and the MANIFEST_DIR directory when nothing else is in
it, and stops, keeping the manifest, if any object with the tag is left. If you exposed the agent,
remove its two rules first:
/ip/firewall/filter/remove [find chain=forward dst-address="AGENT_IP" dst-port="PORT" protocol="tcp" comment="TAG"]/ip/firewall/nat/remove [find chain=dstnat dst-address="LAN_IP" dst-port="PORT" protocol="tcp" comment="TAG"]Then the rest:
:local had [:len [/container/find comment="TAG"]]; :do { /container/stop [find comment="TAG"] } on-error={}; :local s 0; :while (([:len [/container/find comment="TAG" running]] + [:len [/container/find comment="TAG" stopping]]) > 0 && $s < 30) do={ :delay 1s; :set s ($s + 1) }; /container/remove [find comment="TAG"]; :local i 0; :while ([:len [/container/find comment="TAG"]] > 0 && $i < 20) do={ :delay 1s; :set i ($i + 1) }; :if ($had > 0) do={ :local r 0; :while ([:len [/file/find name="ROOT_DIR"]] > 0 && ([:len [/container/find root-dir="/ROOT_DIR"]] + [:len [/container/find root-dir="ROOT_DIR"]]) = 0 && $r < 10) do={ :delay 1s; :set r ($r + 1) }; :if ([:len [/file/find name="ROOT_DIR"]] > 0 && ([:len [/container/find root-dir="/ROOT_DIR"]] + [:len [/container/find root-dir="ROOT_DIR"]]) = 0) do={ :do { /file/remove [find name="ROOT_DIR"] } on-error={} } }; :if ([:len [/container/envs/find list="ENVLIST" key="MIKROSCOPE_TAG" value="TAG"]] > 0) do={ /container/envs/remove [find list="ENVLIST" key!="MIKROSCOPE_TAG"]; /container/envs/remove [/container/envs/find list="ENVLIST" key="MIKROSCOPE_TAG" value="TAG"] }/ip/firewall/address-list/remove [find list="ADDR_LIST" address="NET/30" comment="TAG"]/interface/list/member/remove [find interface="VETH" list="IFACE_LIST" comment="TAG"]/ip/address/remove [find interface="VETH" comment="TAG"]/interface/veth/remove [find name="VETH" comment="TAG"]:if (([:len [/ip/firewall/address-list/find comment="TAG"]] + [:len [/interface/list/member/find comment="TAG"]] + [:len [/ip/firewall/nat/find comment="TAG"]] + [:len [/ip/firewall/filter/find comment="TAG"]] + [:len [/ip/firewall/raw/find comment="TAG"]] + [:len [/ip/firewall/mangle/find comment="TAG"]] + [:len [/ip/route/find comment="TAG"]] + [:len [/container/mounts/find comment="TAG"]] + [:len [/ip/address/find comment="TAG"]] + [:len [/interface/veth/find comment="TAG"]] + [:len [/interface/list/find comment="TAG"]] + [:len [/disk/find comment="TAG"]] + [:len [/container/find comment="TAG"]] + [:len [/container/envs/find list="ENVLIST" key="MIKROSCOPE_TAG" value="TAG"]]) > 0) do={ :error "mikroscope: objects tagged TAG remain, so MANIFEST_FILE stays" }; :if ([:len [/file/find name="MANIFEST_FILE"]] > 0) do={ :if ([:typeof [:find [/file/get [find name="MANIFEST_FILE"] contents] "\ntag=TAG\n"]] = "num") do={ :if ([:len [/file/find name="ROOT_DIR"]] > 0 && ([:len [/container/find root-dir="/ROOT_DIR"]] + [:len [/container/find root-dir="ROOT_DIR"]]) = 0) do={ :do { /file/remove [find name="ROOT_DIR"] } on-error={} }; /file/remove [find name="MANIFEST_FILE"] } }; :if ([:len [/file/find name~"^MANIFEST_DIR/"]] = 0) do={ /file/remove [find name="MANIFEST_DIR" type="directory"] }If a tar is still there because extraction timed out, remove it too:
/file/remove [find name="IMAGE_. Then check that nothing is left; this prints 0:
:put ([:len [/interface/veth/find comment="TAG"]] + [:len [/ip/address/find comment="TAG"]] + [:len [/interface/list/member/find comment="TAG"]] + [:len [/ip/firewall/address-list/find comment="TAG"]] + [:len [/ip/firewall/nat/find comment="TAG"]] + [:len [/ip/firewall/filter/find comment="TAG"]] + [:len [/container/find comment="TAG"]] + [:len [/container/envs/find list="ENVLIST"]] + [:len [/file/find name="MANIFEST_FILE"]])mikroscope uninstall --router … --yes does the same from a computer with the CLI, and verifies
it: Upgrade and uninstall.