Skip to content

Environment variables

Two programs read environment variables, and they read different ones. The CLI on your machine reads MIKROSCOPE_* variables as flag defaults, plus a handful of credentials that have no flag, plus GRAFANA_URL and GRAFANA_TOKEN. The agent on the router reads unprefixed variables (RATE_HZ, TOKEN …) from its container envlist, which install writes. The CLI’s are defined in cmd/mikroscope/, the agent’s in internal/agent/config.go.

  • A variable sets a flag’s default; the flag on the command line wins.

  • A variable set to the empty string is treated as unset.

  • The shape variables (MIKROSCOPE_VETH, MIKROSCOPE_SUBNET, MIKROSCOPE_IFACE_LIST, MIKROSCOPE_ADDR_LIST, MIKROSCOPE_DISK, MIKROSCOPE_LAN_ADDRESS, MIKROSCOPE_REMOTE_IMAGE) set what install writes. status, upgrade and uninstall take the shape of the install on the router instead, from its manifest or its tagged objects, and refuse a variable or flag that contradicts it, naming both values; MIKROSCOPE_REMOTE_IMAGE is the exception, filled in for status and uninstall and never refused. .env.example leaves them unset for that reason.

  • The CLI never reads a .env file. Export it into the shell first:

    Terminal window
    set -a; . ./.env; set +a
  • Quote a URL that holds & when it lives in a file you source: unquoted, & is a shell operator. .env.example quotes MIKROSCOPE_INFLUX_URL for that reason.

Most deployment flags have no variable. --rate, --buffer, --port, --memory-max, --mem-limit-mb, --capture-mb, --triggers, --floor-hz, --privileged, --ephemeral, --expose, --restart-max-count, --restart-interval, --start-on-boot, --container-name and --extract-timeout are set on the command line or not at all. CLI has every flag.

Variable Flag Read by Default in the code Value in .env.example
MIKROSCOPE_ROUTER --router doctor, install, upgrade, uninstall, status none (required) set
MIKROSCOPE_SSH_PORT --ssh-port the same ssh config 22
MIKROSCOPE_SSH_KEY --ssh-key the same ssh agent or config empty
MIKROSCOPE_SSH_OPTIONS --ssh-option the same none commented out, StrictHostKeyChecking=accept-new
MIKROSCOPE_ARCH --arch the deployment verbs auto commented out, auto
MIKROSCOPE_AGENT_TAR --agent-tar doctor, plan, install, upgrade, image empty (build the agent here) commented out
MIKROSCOPE_REMOTE_IMAGE --remote-image the deployment verbs empty (upload a tar) commented out
MIKROSCOPE_NAME --name the deployment verbs mikroscope commented out
MIKROSCOPE_VETH --veth the deployment verbs veth-mikroscope commented out
MIKROSCOPE_SUBNET --subnet the deployment verbs, record, forward 172.30.10.0/30 commented out
MIKROSCOPE_IFACE_LIST --iface-list the deployment verbs LAN; none joins no list commented out
MIKROSCOPE_ADDR_LIST --addr-list the deployment verbs LANs; none joins no list commented out
MIKROSCOPE_DISK --disk the deployment verbs empty (internal flash) commented out, tmpfs
MIKROSCOPE_LAN_ADDRESS --lan-address the deployment verbs empty commented out
MIKROSCOPE_TOKEN --token the deployment verbs, record, forward empty commented out

MIKROSCOPE_SSH_OPTIONS is a comma-separated list of Key=value pairs, the same as repeating --ssh-option; the first --ssh-option on the command line replaces the whole list. CLI has the keys it takes.

MIKROSCOPE_TOKEN is two things at once. For install and upgrade it is the token written into the agent’s envlist, which the agent then requires; the CLI hands the command that writes it to ssh on its standard input, never on a command line. status and uninstall do not need it. For record, forward and doctor’s health section it is the token sent to the agent over the direct transport; without it, doctor skips the ring of an agent that has one. The envlist is not a secret store: any RouterOS user with the read policy can list every container’s envlist over the API, which is why the token is the only credential that goes there.

Variable Flag Read by Meaning
MIKROSCOPE_API_ADDR --api record, mark, forward RouterOS binary API host:port; .env.example has 192.168.88.1:8728
MIKROSCOPE_API_USER --api-user record, mark, forward the dedicated API user
MIKROSCOPE_API_PASSWORD none record, mark, forward that user’s password; there is deliberately no flag
MIKROSCOPE_INTERFACES --interfaces forward comma-separated interfaces for monitor-traffic; .env.example has bridge,ether1 commented out

The relay, --log-markers and the API tier fail, or in forward’s case are disabled with a warning, unless --api, --api-user and MIKROSCOPE_API_PASSWORD are all set. The API user has the policy that user needs.

Variable Flag Default in the code Meaning
MIKROSCOPE_INFLUX_URL --influx empty InfluxDB 3 server, http://host:8181; a full write URL is still taken verbatim
MIKROSCOPE_INFLUX_DB --influx-db empty the database --influx writes to, when --influx is a bare server
MIKROSCOPE_POSTGRES_DSN --postgres empty PostgreSQL connection string for the connecting SQL sink
MIKROSCOPE_LOKI_URL --loki empty Loki push URL
MIKROSCOPE_LOKI_TENANT --loki-tenant empty X-Scope-OrgID
MIKROSCOPE_OTLP_URL --otlp empty OTLP/HTTP metrics endpoint
MIKROSCOPE_GRAPHITE_ADDR --graphite empty carbon plaintext host:port
MIKROSCOPE_ELASTIC_URL --elastic empty Elasticsearch or OpenSearch base URL
MIKROSCOPE_TELEGRAF_URL --telegraf empty Telegraf listener URL (http://, tcp:// or udp://)
MIKROSCOPE_HOST_TAG --host-tag router host tag on every point in every sink; .env.example has it commented out

Setting a sink’s URL variable is enough to turn that sink on: forward builds every sink whose flag ends up non-empty, from the command line or from the variable. Every one of these names carries the MIKROSCOPE_ prefix: a bare LOKI_URL is read by nothing.

A flag is visible in ps and in a shell history, so every secret the CLI uses is read from the environment only.

Variable Used for
MIKROSCOPE_API_PASSWORD the RouterOS API user
MIKROSCOPE_INFLUX_TOKEN InfluxDB, sent as Authorization: Bearer <token>
MIKROSCOPE_LOKI_TOKEN Loki
MIKROSCOPE_OTLP_TOKEN the OTLP endpoint
MIKROSCOPE_ELASTIC_AUTH Elasticsearch or OpenSearch
MIKROSCOPE_TELEGRAF_TOKEN the Telegraf HTTP listener
GRAFANA_TOKEN dashboards publish, import and check, forward --grafana, uninstall --targets dashboard

GRAFANA_TOKEN is the one Grafana token every verb uses, with no prefix, and no flag takes it. The Grafana itself is --grafana, and each verb looks for a default in a different order:

Verb Reads, after --grafana
dashboards publish, uninstall --targets dashboard MIKROSCOPE_GRAFANA_URL, then GRAFANA_URL
dashboards import, dashboards check GRAFANA_URL, then MIKROSCOPE_GRAFANA_URL
forward MIKROSCOPE_GRAFANA_URL only

forward never reads GRAFANA_URL, so a GRAFANA_URL on its own does not make the collector publish. Other Grafana tools read that name and GRAFANA_TOKEN too, and a collector started from a shell set up for one of them would start writing a folder, a datasource and a dashboard into that Grafana without anyone asking it to. The file .env.example carries every Grafana variable, commented out, in its two Grafana blocks.

forward, dashboards publish and uninstall take --grafana and the flags below, and their variables do carry the prefix, because they are collector settings rather than the dashboards import and check ones:

Variable Flag Default in the code Meaning
MIKROSCOPE_GRAFANA_URL --grafana empty the Grafana to publish to; empty publishes nothing
MIKROSCOPE_GRAFANA_FOLDER --grafana-folder mikroscope folder to publish into; an empty variable is ignored, so the General folder takes --grafana-folder ""
MIKROSCOPE_GRAFANA_DATASOURCE_UID --grafana-datasource-uid empty adopt this datasource instead of creating one; one store per run
MIKROSCOPE_GRAFANA_DATASOURCE_URL --grafana-datasource-url empty the address Grafana queries, for the sinks that cannot know it; one store per run
MIKROSCOPE_GRAFANA_DATASOURCE_SSLMODE --grafana-datasource-sslmode empty sslmode for the PostgreSQL datasource: disable, require, verify-ca or verify-full, nothing else

uninstall reads only MIKROSCOPE_GRAFANA_URL and MIKROSCOPE_GRAFANA_DATASOURCE_UID of these, and ignores the others.

A run that publishes more than one store and sets MIKROSCOPE_GRAFANA_DATASOURCE_UID or MIKROSCOPE_GRAFANA_DATASOURCE_URL is refused before any request: each names one datasource. Publish the store that needs one on its own with dashboards publish (Grafana publishing).

Publishing refuses to write without GRAFANA_TOKEN: some Grafanas accept an anonymous request, and one that did would write as whoever the server thinks is asking. --grafana-dry-run writes nothing and sends no request, so it runs without the token.

The agent reads these from its container’s environment when it starts. An invalid value makes it print one line starting mikroscope-agent: bad configuration:, which RouterOS puts in its log under the container topic, and exit with status 2.

Variable Agent default Accepted Meaning
RATE_HZ 10 1–100 sampler rate
BUFFER_S 60 10–3600 ring length in seconds
PORT 9123 1–65535 HTTP port
ADDR empty an address bind address. Empty binds :PORT, every address in the container’s network namespace; install always sets it to the agent’s veth address
TOKEN empty when set, every path but /healthz requires Authorization: Bearer <token>
IRQ_TOP_K 8 0–64 interrupt lines kept per sample, busiest first; 0 ships no interrupt lines and no irq_total
MEM_LIMIT_MB 14 8–1024 Go soft memory limit in MiB
FLOOR_HZ 0 0–1000 0 keeps the per-source floors; N puts every level source on N Hz and turns off emit-on-change
SOURCES empty (every source detected) comma-separated names restrict the optional sources to this list; /proc/stat is always read
TRIGGERS softnet-drop,oom,kmsg<=3,reset,irq-err,flash-bad conditions, comma-separated capture trigger conditions
CAPTURE_MB 4 0–256 pinned-bytes budget for captures; 0 turns them off
CAPTURE_PRE_S 5 1–60 seconds kept before the sample a trigger fires on
CAPTURE_POST_S 5 1–60 seconds kept after it
CAPTURE_POLICY first first or last on a full budget: first refuses the new capture, last evicts the oldest
TRIGGER_REFRACTORY_S 10 0–3600 quiet time per condition after it fires
PROC_ROOT /proc a path where /proc is read from; the kernel log is /dev/kmsg only when this is /proc, otherwise dev/kmsg beside the given directory
SYS_ROOT /sys a path where /sys is read from

The names SOURCES accepts are the optional sources /capabilities reports: meminfo, loadavg, softnet, softirqs, interrupts, vmstat, psi, schedstat, self, buddyinfo, yaffs, diskstats, slabinfo, kmsg, thermal, cpufreq, mtd and perf. The trigger conditions and what each compares are on Triggered capture.

Before it listens, the agent checks the ring against the container’s own memory.max: RATE_HZ × BUFFER_S × 3 456 B plus CAPTURE_MB must fit, or it refuses to start and names the three variables and --memory-max. If that figure is more than half of MEM_LIMIT_MB, it starts and logs a warning with the limit to raise to. 3 456 B is not itself a measurement: it is the Go allocator size class that serves a line with every source on (3 230 B, 4 cores, IRQ_TOP_K 8), because that class is what the heap is charged. A board with more cores or more interrupt lines writes longer lines, so the check errs open. When the agent cannot read its memory.max, the refusal check does not run.

install and upgrade write the envlist <name>-env with these entries, in this order, and nothing else. The install’s shape (its lists, disk and exposure) is not in the envlist: install records it in the install manifest, mikroscope/<name>.manifest.txt on the install’s disk, which holds no secret either.

The entries install writes into the agent's envlist
KeyWrittenFromHolds
MIKROSCOPE_TAGalways--namethe ownership marker mikroscope:<name> (managed by mikroscope), written first and removed last; the agent ignores it
RATE_HZalways--rate, default 10, 1–100the sampler rate, in Hz
BUFFER_Salways--buffer, default 60, 10–3600the ring's length, in seconds
PORTalways--port, default 9123, 1–65535the agent's HTTP port
ADDRalways--subnetthe agent's address, the .2 of the /30; the agent binds only there
MEM_LIMIT_MBalways--mem-limit-mb, 8–1024the agent's Go soft memory limit, in MiB; derived from the ring (rate × buffer × line, × 2.5, at least 16 MiB, at most three quarters of --memory-max while that still holds the ring) unless --mem-limit-mb gives it
FLOOR_HZonly when above 0--floor-hz, default 0, 0–1000one cadence for every level source, in Hz
CAPTURE_MBalways--capture-mb, default 4, 0–256the triggered-capture budget, in MiB; 0 turns captures off
TRIGGERSonly when set--triggersthe trigger conditions; unset, the agent uses its default set
TOKENonly when set--tokenthe bearer token the agent requires, from --token or MIKROSCOPE_TOKEN, with or without --expose