# RouterOS script

Generate the install as one RouterOS script with mikroscope plan --rsc, run it from the router's terminal or with /import, verify it, read its manifest and remove it.

Source: https://jmrplens.github.io/mikroscope/install/script/

`mikroscope plan --rsc` writes the whole install as one RouterOS script. Run it on the router, from
a terminal or with `/import`: no ssh from your computer, and the script checks the router before it
writes anything. To fill in the options in a form instead, use the
[Script generator](https://jmrplens.github.io/mikroscope/install/generator/); it writes the same script.

## Generate

```sh
mikroscope plan --rsc --remote-image jmrplens/mikroscope-agent:1.6.1 --out install.rsc
```

It connects to nothing and prints
`install.rsc: 39 lines; review it, then paste it into the router's terminal or /import it`.
Without `--out` the script goes to standard output. It takes the same flags as `install`: `--name`,
`--veth`, `--subnet`, `--iface-list`, `--addr-list`, `--rate`, `--disk` and the rest
([CLI](https://jmrplens.github.io/mikroscope/reference/cli/#flags-of-the-deployment-verbs)).

- **Registry pull**, with `--remote-image`: the router pulls the image itself.
- **Image tar**, without it: the script expects the tar on the router as `mikroscope.tar`
  (`<name>.tar` with `--name`), and its header says so (`BEFORE RUNNING: put the agent image tar on the device as mikroscope.tar`). Upload
  it first: [Offline install](https://jmrplens.github.io/mikroscope/install/offline/).

With the default options the script is:

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

It runs as one `{ … }` block, in this order:

1. **Guards.** It stops with `:error "mikroscope: …"`, before anything is written, when the RouterOS
   release is before 7.24, the `container` package is missing, device mode is off, a veth, an
   envlist or a `--container-name` container of that name belongs to something else, the interface
   list does not exist, the `--disk` is missing, the tar is not uploaded, or a file at the
   manifest's path is not this install's manifest.
2. **The install manifest**, then each object, with the same tag `install` writes. A step that fails
   stops the steps after it.
3. **A wait of up to 120 s** for the container to run, then one line: `mikroscope: agent running,
   http://172.30.10.2:9123/healthz` or `mikroscope: not running yet; see /log/print where
   topics~"container"`.

## Run on the router

- **Paste**

  1. Open a terminal on the router: WebFig › Terminal, Winbox › New Terminal, or ssh.
  2. Wait for the `] >` prompt.
  3. Paste the whole file.

- **Upload and import**

  1. Upload `install.rsc` in Files (WebFig or Winbox), or with `scp`.
  2. Run:

     ```routeros
     /import file-name=install.rsc
     ```

  3. Delete the file: `/file/remove install.rsc`.

> **Paste only at the prompt**
>
> On a first login RouterOS asks `Do you want to see the software license? [Y/n]:`, and the first
> lines you paste answer that question and are lost. A paste that loses the block's opening `{`
> breaks. Answer the question, wait for `] >`, then paste.

A successful `/import` prints:

```text
mikroscope: agent running, http://172.30.10.2:9123/healthz
Script file loaded and executed successfully
```

A guard that fails names the problem and writes nothing, for instance with the tar route and no tar
uploaded:

```text
Script Error: mikroscope: upload mikroscope.tar first (:error; line 20) (:import; line 1)
```

## 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")
```

The first shows the container with the `R` (running) flag; the second prints the agent's
`/healthz` answer, `{"ok":true,…}`. From a computer with the CLI, `mikroscope status --router …`
recognises the install and probes the agent.

## Install manifest

The script writes the install manifest first, `mikroscope/<name>.manifest.txt` on the install's
disk. Read it on the router:

```routeros
:put [/file/get [find name="mikroscope/mikroscope.manifest.txt"] contents]
```

```text
mikroscope-manifest=1
name=mikroscope
tag=mikroscope:mikroscope (managed by mikroscope)
disk=
veth=veth-mikroscope
subnet=172.30.10.0/30
port=9123
iface-list=LAN
addr-list=LANs
expose=
container-name=
remote-image=registry-1.docker.io/jmrplens/mikroscope-agent:1.6.1
token=no
dir=mikroscope
file=mikroscope/mikroscope.manifest.txt
object=/interface/veth name=veth-mikroscope
object=/ip/address interface=veth-mikroscope
object=/interface/list/member interface=veth-mikroscope list=LAN
object=/ip/firewall/address-list list=LANs address=172.30.10.0/30
object=/container/envs list=mikroscope-env
object=/container interface=veth-mikroscope
dir=mikroscope/mikroscope
```

The first lines are the options the install was made with, under the CLI's flag names; `token=`
says only `yes` or `no`, never the value. Each `object=`, `file=` and `dir=` line is something the
install created. `mikroscope uninstall` reads the manifest, removes everything it lists and the
manifest last. The tar route adds `file=mikroscope.tar`.

## Limitations

- **It cannot upload anything.** Pair it with `--remote-image`, or upload the tar before you run it.
- **With a token, the file is a credential.** The envlist line carries the token in clear, because
  the router needs it in clear. The CLI writes the file `0600`; delete it from the router after
  `/import`.
- **It runs no `doctor`.** The guards cover the release, the package, device mode, name collisions,
  the interface list, the disk, the tar and the manifest path. Free memory and storage, a route
  that overlaps the /30 and a firewall rule that drops the agent's replies are not checked: run
  `mikroscope doctor` from a computer that can reach the router, or check the
  [Requirements](https://jmrplens.github.io/mikroscope/install/prerequisites/) by hand.

What has been run, and what has not, is on [Tested on](https://jmrplens.github.io/mikroscope/about/status/#install-routes-tested).

## Remove

From a computer with the CLI:

```sh
mikroscope uninstall --router admin@192.168.88.1 --yes
```

It reads the manifest, so it needs no other flag. Without the CLI, run the removal commands on
[Manual install: terminal](https://jmrplens.github.io/mikroscope/install/manual-cli/#remove).
[Upgrade and uninstall](https://jmrplens.github.io/mikroscope/install/upgrade/) has what removal never touches.

## See also

- [Script generator](https://jmrplens.github.io/mikroscope/install/generator/): the same script from a form in the browser.
- [Offline install](https://jmrplens.github.io/mikroscope/install/offline/): the image tar the tar route needs.
- [Installer safeguards](https://jmrplens.github.io/mikroscope/security/installer/#a-generated-rsc-script-is-a-credential):
  why a script with a token is a credential.
- [Install methods](https://jmrplens.github.io/mikroscope/install/routes/): the other ways to install.
