What the installer refuses
The installer writes to a router it did not configure, over the operator’s own admin ssh session,
so there is no privilege boundary between a mistake and the router. What stands in for one is a
set of refusals. This page lists them: what install stops on before it writes, what it will not
touch, what it treats as a failure, and how uninstall shows that it is done rather than saying so.
Nothing is written before it is listed
Section titled “Nothing is written before it is listed”install builds the image, prints every command it would run with its exact RouterOS text, and
then, in this order:
- runs
doctor, the read-only preflight, unless--no-doctoris given; a missing prerequisite stops it withN prerequisite(s) missing; nothing was written; - asks
write the objects above to the router? [y/N], unless--yesis given; anything butyorYstops it withnot confirmed; nothing written; - only then writes.
plan, and install --dry-run, stop after the listing. The listing masks the token as
value="(token)".
upgrade does not get this guarantee. It builds the image, refuses a router where any step of the
plan built from its own flags is missing (nothing to upgrade: run install first), and asks the
same write the objects above to the router? [y/N] — but it prints no listing and runs no doctor
first, so there are no objects above. On y it removes the container, the envlist and the image
and writes them again, the envlist from the flags given to upgrade; what –expose
opens has what that means for the token.
What install writes to your router
- a veth
- one address
- one interface-list membership
- one address-list entry
- an envlist
- the image tar, unless
--remote-imagehas the router pull the image - the container
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.
Objects it does not own
Section titled “Objects it does not own”Before writing, install asks the router three questions about every step in one ssh connect:
is our object there, does something with the same effect exist, and does it carry our tag. An
object that exists but does not carry mikroscope’s tag stops install, naming the step:
veth interface veth-mikroscope exists on the router and was not created by mikroscope (no ownership tag); pick another --name/--veth/--subnet, or remove it by hand if it is yoursWhat counts as “the same effect” is the object’s identity, not its name alone:
| Step | Collides with any existing | Ours when it carries |
|---|---|---|
| veth | /interface/veth with the same name |
the tag in comment |
| router address | /ip/address on that veth |
the tag in comment |
| interface-list membership | member with that interface in that list | the tag in comment |
| address-list membership | entry with the /30 in that list | the tag in comment |
| expose dst-nat | dstnat rule with that destination address, port and protocol |
the tag in comment |
| expose forward accept | forward rule with the container’s address, that port and protocol |
the tag in comment |
| container | a container using the same image file, an envlist named <name>-env, or a file at the image path |
a container with the tag; an envlist holding the marker |
Scroll sideways to see every column
A step that is already ours is skipped, so running install twice creates nothing the second time.
The refusal happens when install reaches the colliding step. Steps before it that were absent
have already been created; they carry the tag, and uninstall removes them. uninstall never
touches the foreign object.
The envlist and the image carry no comment
Section titled “The envlist and the image carry no comment”Neither /container/envs nor /file has a comment field, so the container step signs them another
way: the first entry written to the envlist is MIKROSCOPE_TAG holding the exact tag, and the
image file counts as ours only while that marker exists. A foreign envlist with the same name, a
foreign file at the image path, or another container’s envlist is therefore foreign, and stops
install. Leftovers of an earlier mikroscope install — an envlist under our marker with no
container — are ours to replace, and install clears them before writing new ones.
Selectors are exact
Section titled “Selectors are exact”Every object install creates carries the comment mikroscope:<name> (managed by mikroscope),
verbatim. Every network object is removed by that exact comment together with the identity its check used —
comment="…", never a pattern match. The container is removed by the comment alone, and the
envlist and the image, which carry no comment, by list="<name>-env" and the exact image file name,
only while the marker entry holding the exact tag exists. So uninstall cannot reach a hand-made
setup, or anything else whose comment or name shares a substring with the container name.
Every find quotes address and port attributes. Unquoted, RouterOS parses them as typed values and
the comparison with the stored one comes back empty; verified for both on RB5009UG+S+, RouterOS 7.24.2, .
A write that prints is a failure
Section titled “A write that prints is a failure”A RouterOS write prints nothing on success, and over ssh it reports errors as text with exit status
0, abandoning the rest of a ;-joined line. So install treats any output from a write as a
failure (create <step>: router said "…") and stops there.
The image tar is uploaded with scp before the container step runs, and before the marker exists.
If that step then fails, install removes the uploaded file (undo removed the uploaded …);
otherwise it would count as a foreign file on every later attempt.
uninstall reads output the same way in the other direction: a removal that printed something is
reported as skip, not gone.
Values it will not put in a command
Section titled “Values it will not put in a command”Every flag that reaches a RouterOS command is interpolated into it verbatim. There is no privilege to escalate — the command runs as your admin — but a quote or a semicolon would turn a clear error into a confusing RouterOS syntax error, or a selector into something wider than intended. So every value is checked before the first connect, and one outside these bounds stops the command with the rule it broke:
| Flag | Accepted |
|---|---|
--name |
^[A-Za-z0-9][A-Za-z0-9_ |
--veth, --iface-list, --addr-list |
^[A-Za-z0-9][A-Za-z0-9_ |
--disk |
^[A-Za-z0-9][A-Za-z0-9_, or empty for the internal flash; --ephemeral forces tmpfs |
--arch |
^[a-z0-9]{1,16}$ |
--token |
^[A-Za-z0-9_.-]{0,128}$ |
--subnet |
an IPv4 /30, written as its network address |
--port |
1–65535 |
--rate |
1–100 Hz |
--buffer |
10–3600 s |
--memory-max |
^\d{1,6}[KMG]?$ |
--mem-limit-mb |
8–1024 |
--floor-hz |
0–1000 |
--capture-mb |
0–256 |
--expose |
needs --lan-address as an IPv4 address, and a non-empty token |
--triggers |
the agent’s own condition list, parsed by agent.ParseTriggers |
--remote-image |
a registry reference: owner/name:1.0.0, with or without a host, no quote, space or semicolon |
Scroll sideways to see every column
There is no exception. --triggers is checked by the same gate as the rest: Finish hands it to
agent.ParseTriggers, the agent’s own parser, which is the authority on what a condition means.
An unknown condition, a malformed threshold, a quote or a semicolon fails the verb with exit
status 2 before the first connect, and nothing is written.
The agent parses TRIGGERS again when it starts, because the envlist can be edited on the router
by hand. On a value it cannot parse it exits non-zero with a mikroscope-agent: bad configuration: …
line in the router log; under the on-failure policy RouterOS may retry it up to five times. No run
with a bad TRIGGERS on the router is recorded.
What it refuses to send to the router
Section titled “What it refuses to send to the router”Two of the four install routes hand the router something the CLI did not build, and each gets a check of its own before anything is written.
--agent-tar <file>, the image tar the release publishes, is read and inspected on your host first. It has to be a docker-save tar of exactly one image with one layer whose entrypoint is/mikroscope-agent, and its architecture has to match--arch; otherwise the verb stops, and for a mismatch it names the asset to download instead (--agent-tar … is a linux/arm64 image and --arch says arm: download the mikroscope-agent-arm.). That check says the tar is a mikroscope agent image of the right architecture. It does not say the tar is the one the release published — verify it againsttar asset instead checksums.txtfrom the release, and its cosign signature if you use one, before you pass it.--remote-image <reference>makes the router pull the image itself, so nothing is uploaded and no tar lands on the device. The reference is matched against a registry-reference pattern before it reaches the command line, because RouterOS takes it inside a quoted string on a;-joined line. The router then needs to reach that registry over its own network, and it takes the registry host from/, a setting global to the device and shared with every other container on it and ships ascontainer/ config registry-url https:/. mikroscope never writes that setting./ registry-1. docker. io doctorreads it, and when the reference names a host the setting does not match it prints the one command to run (/, for the GHCR copy of the image) or says to usecontainer/ config/ set registry-url=https:/ / ghcr. io --agent-tarinstead. The Docker Hub referencejmrplens/carries no host and leaves the setting as the router has it. Trust in the image is trust in that registry: nothing in the CLI verifies what the router pulls.mikroscope-agent:1. 0. 0
A generated .rsc script is a credential
Section titled “A generated .rsc script is a credential”plan --rsc writes the install as a RouterOS script for a router you reach only through WinBox or
WebFig. It carries the same commands install runs, in the same order, with the same tags — and,
when --token or MIKROSCOPE_TOKEN is set, the envlist line carries the token in clear, because
the router needs it. The script says so in its own header. Treat the file the way you treat the
token: do not commit it, do not paste it where it is logged, and delete it from the router’s Files
after /import. Without a token it holds no secret, only the plan.
plan and install --dry-run mask the token in what they print to the terminal
(value="(token)"); --rsc cannot, since the script has to run.
How uninstall proves it is done
Section titled “How uninstall proves it is done”uninstall runs every removal newest first, ignoring what is already gone. Then it asks the router,
in one connect, how many objects each step created are still there, prints one line per step with
the count, and fails naming every step whose count is not zero
(uninstall left objects behind: …). A removal that printed nothing is not evidence; the count is.
status runs the same count on its own.
The container step is the slow one, and the order inside it is what keeps the count honest:
- the container is stopped (guarded, because stopping a stopped container is an error) and removed;
/container/removereturns before the container is gone, and a/file/removeof the image issued meanwhile did nothing, silently (RB5009UG+S+, RouterOS 7.24.2, ) — so it waits up to 20 s for the container to vanish, then retries the file removal for up to 15 s;- the rest of the envlist goes, and the marker goes last, only once the file is gone.
If the file removal does not take, the marker stays, the count keeps including the envlist and the
file, and uninstall says so instead of reporting clean.
A doctor → install → status → upgrade → uninstall round trip (make roundtrip) left the router’s /export byte-identical, compared by hash (verified on RB5009UG+S+, RouterOS 7.24.2, ).