# Script generator

Fill in a form and get the RouterOS script that installs the agent, the same script `mikroscope plan --rsc` prints, and the script that removes it again.

Source: https://jmrplens.github.io/mikroscope/install/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

- The router runs RouterOS 7.24 or later, with the `container` package and
  `device-mode container=yes`
  ([Requirements](https://jmrplens.github.io/mikroscope/install/prerequisites/)). 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](https://jmrplens.github.io/mikroscope/install/firewall/).
- On a CHR with the free licence, the router sends at most 1 Mbit/s, which a
  rate of 50 Hz reaches ([Tested on](https://jmrplens.github.io/mikroscope/about/status/#virtual-lab)).

## Settings

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

## Install script

```routeros
# 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:

```sh
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.

## 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:

```sh
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.tar
```

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.

## Run the script

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.

## Verify

On the router:

```routeros
/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:

```sh
curl http://172.30.10.2:9123/healthz
```

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

## 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.

With the CLI:

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

Uninstall script:

```routeros
# 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.

## See also

- [Install methods](https://jmrplens.github.io/mikroscope/install/routes/): every way to install, and
  when to use each.
- [Install with the CLI](https://jmrplens.github.io/mikroscope/install/): the same install from a
  computer, with `doctor` first.
- [RouterOS script](https://jmrplens.github.io/mikroscope/install/script/): the same script from
  `mikroscope plan --rsc`, and what each part does.
- [Manual install: terminal](https://jmrplens.github.io/mikroscope/install/manual-cli/): the same
  commands one at a time, with placeholders.
- [Offline install](https://jmrplens.github.io/mikroscope/install/offline/): choosing, downloading and
  verifying the image tar.
- [Storage and container settings](https://jmrplens.github.io/mikroscope/install/layout/): the disk, the
  root and the container's settings.
- [Network access](https://jmrplens.github.io/mikroscope/install/reaching-the-agent/): reaching the
  agent from the collector.
- [Installer safeguards](https://jmrplens.github.io/mikroscope/security/installer/): the bounds every
  value passes before it reaches a command.
