Configuration reference
A collector runs one of two runtimes — Grafana Alloy (config via remotecfg)
or otelcol-contrib (config via OpAMP) — and each leaves a different set of files
on the host. The optional linkmesh-agent onboarding helper, when present,
leaves its own files too. This page lists every location that matters — what
owns it, when to touch it, and when to leave it alone. The server’s own
authentication, storage and database configuration is further down.
Source-of-truth model
Section titled “Source-of-truth model”Read this once before editing anything.
┌────────────────────────────┐
│ LinkMesh server │
Operator edits ────► │ (UI / API / git-backed │
│ config store) │
└────────────┬────────────────┘
│ remotecfg poll (Alloy)
│ OpAMP push (otelcol-contrib)
▼
┌────────────────────────────┐
│ Collector runtime │
│ (upstream Alloy or │
│ upstream otelcol-contrib) │
└────────────────────────────┘
The collector fetches its config directly from the server — over Alloy’s
remotecfg (Connect-RPC) or otelcol’s OpAMP (WebSocket). The collector process
itself is self-managed: Alloy applies the config it pulls via remotecfg, and
otelcol-contrib is supervised by opampsupervisor. The optional LinkMesh agent,
when present, does service / log-source discovery and reports host context for
onboarding; it does not install, supervise, or configure the collector and
is not in the config path. See
Native remote config for the framing.
Rule of thumb:
| File / surface | Owned by | Survives apt upgrade? |
Survives server config push? |
|---|---|---|---|
/etc/linkmesh/config.yaml (optional agent, VM) |
Operator / enrollment script | yes (config|noreplace) |
yes — the server never rewrites it |
/etc/alloy/config.alloy (bootstrap) |
Operator (Alloy runtime) | yes | yes — the pipeline arrives via remotecfg, not this file |
/etc/otelcol-contrib/config.yaml (bootstrap) |
Operator (otelcol + OpAMP runtime) | yes | yes — the pipeline arrives via OpAMP, not this file |
If you edited a pipeline component on the host and your change vanished from the topology, you edited the wrong layer — bootstrap files on disk only carry the connection back to the server. The real pipeline lives in the LinkMesh UI and is delivered to the collector at runtime; it is not persisted to disk by default.
/etc/linkmesh/config.yaml — agent config (optional onboarding agent)
Section titled “/etc/linkmesh/config.yaml — agent config (optional onboarding agent)”The agent’s config file. On a VM (.deb / .rpm) it lives at
/etc/linkmesh/config.yaml — the systemd unit runs
linkmesh-agent --config /etc/linkmesh/config.yaml. In a container / the
Kubernetes DaemonSet it is mounted at /etc/linkmesh-agent/config.yaml. The
--config flag is required; there is no built-in default path.
A minimal config is just the server URL and the enrollment token:
server:
url: https://your-server.example.com # HTTPS base URL; the control channel is
# a WebSocket at /v1/agent
token: <enrollment-token> # Bearer credential, sent on the WS upgrade
The agent connects outbound over HTTPS/WSS — there is no inbound port and no mTLS client certificate. Its identity is bound server-side from the enrollment token; see Enrollment tokens.
Full config surface
Section titled “Full config surface”| Block / key | Type | Default | Notes |
|---|---|---|---|
server.url |
string | required | HTTPS base URL, e.g. https://app.linkmesh.io. A legacy grpcs://…:50051 value is accepted and normalised to the WSS /v1/agent endpoint. |
server.token |
string | — | Enrollment token, sent as a Bearer credential on the WebSocket upgrade. |
agent.id |
string | derived | Optional stable agent identifier. |
agent.environment |
string | production |
Free-form environment label. |
certificates.caCertPath |
string | system trust store | Optional CA bundle used to verify the server’s TLS certificate. |
certificates.insecureSkipVerify |
bool | false |
Lab-only; skips server-cert verification (see the aside below). |
collector.binaryPath / configPath / serviceName / collectorType |
string | auto-detected | The managed collector. Unset → the agent detects an installed Alloy / otelcol-contrib. |
collector.nativeRemoteConfig |
bool (nullable) | alloy → true, else false |
When true, the collector self-manages its config (Alloy remotecfg / OpAMP) and the agent does not deliver config. An explicit value wins over the per-type default. See Native remote config. |
collector.metricsUrl |
string | alloy → :12345/metrics, else :8888/metrics |
Liveness-probe endpoint only. |
collectors[] |
list | — | Multi-collector form; supersedes the single collector block when set. Each entry needs a unique name (derived from serviceName or position if left blank). |
logging.level |
string | info |
debug / info / warn / error. |
logging.file |
string | — | Optional log file; default is journald / stdout only. |
customFingerprints[] |
list | — | Operator-defined service-detection matchers + pipeline templates (extends detect). |
Log browsing (browse)
Section titled “Log browsing (browse)”The agent can list directories and sample files on the host so the onboarding UI can preview logs. It’s on by default and log-scoped; every key is optional.
| Key | Type | Default | Notes |
|---|---|---|---|
browse.enabled |
bool | true |
Master switch. Set false to refuse all directory listing / file sampling on this host — the UI then shows “log browsing is disabled by agent configuration”. |
browse.allowedRoots |
list | /var/log, /var/lib/docker/containers, /opt |
Directory prefixes the agent may browse. The sentinel "*" (or "/") grants full-filesystem scope — see the warning below. |
browse.denyPatterns |
list | keys/certs (*.key, *.pem, id_rsa*), *shadow*, .ssh, /proc/*, /sys/*, /etc/shadow |
Globs that are never browsable, even inside an allowed root. Always enforced. |
browse.maxDirEntries |
int | 1000 |
Cap on entries returned per directory listing. |
browse.maxSampleLines |
int | 200 |
Cap on lines returned when sampling a file in line mode. |
browse.maxSampleBytes |
int | 262144 (256 KiB) |
Cap on bytes returned when sampling a file in byte mode. |
/etc/alloy/config.alloy — Alloy bootstrap
Section titled “/etc/alloy/config.alloy — Alloy bootstrap”Used by the Alloy runtime. The operator writes it once when installing the
collector: a remotecfg block pointing at the LinkMesh server plus an optional
own_metrics push for the topology canvas.
See Onboard Grafana Alloy via remotecfg
for the full template.
Key fields:
| Field | Notes |
|---|---|
remotecfg.url |
Server base URL (e.g. https://linkmesh.example.com). Alloy appends the CollectorService path itself — do not add /v1/opamp here (that’s OpAMP, a different protocol). |
remotecfg.id |
Stable identifier for this collector. constants.hostname is fine for most fleets. |
remotecfg.bearer_token |
Per-collector OTLP token minted in the LinkMesh UI (or via POST /api/v1/collectors/{id}/otlp-token). Bearer scheme only — basic_auth is rejected. |
otelcol.exporter.otlphttp.linkmesh Authorization header |
Same token as bearer_token, used by own_metrics push to /v1/metrics. |
The pipeline itself (sources, processors, exporters) does not live here. Alloy fetches it via remotecfg on every poll and merges it into the runtime config in memory.
/etc/otelcol-contrib/config.yaml — otelcol bootstrap (otelcol + OpAMP runtime)
Section titled “/etc/otelcol-contrib/config.yaml — otelcol bootstrap (otelcol + OpAMP runtime)”Bootstrap config for the upstream OpenTelemetry Collector when onboarding it
via OpAMP. The collector starts with a nop pipeline; LinkMesh
replaces it with the real pipeline over OpAMP within seconds of the first
handshake. See Onboard otelcol-contrib via OpAMP
for the full template.
Key fields:
| Field | Notes |
|---|---|
extensions.opamp.server.ws.endpoint |
wss://<server>/v1/opamp — same host/port as the web UI, only the path differs. Use wss:// over any network; ws:// for loopback only. |
extensions.opamp.server.ws.headers.Authorization |
Bearer <ENROLLMENT_TOKEN>. Single-use enrollment token minted in the UI. |
extensions.opamp.capabilities |
Only reports_effective_config and reports_health. Adding other capability keys makes otelcol-contrib refuse to start with “invalid keys”. |
Like Alloy’s bootstrap, the pipeline is delivered at runtime — the YAML on disk only carries the OpAMP wiring.
systemd units
Section titled “systemd units”linkmesh-agent.service (optional onboarding agent)
Section titled “linkmesh-agent.service (optional onboarding agent)”[Unit]
Description=LinkMesh Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/linkmesh-agent --config /etc/linkmesh/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
The agent does not depend on the collector’s unit, and the collector does not depend on the agent’s. The agent runs independently so it can do discovery and report host context for onboarding.
alloy.service (upstream Grafana package)
Section titled “alloy.service (upstream Grafana package)”Installed by apt install alloy / dnf install alloy from the upstream Grafana
package. The unit ships with the package; LinkMesh does not template it. Reads
/etc/alloy/config.alloy.
sudo systemctl status alloy
sudo journalctl -u alloy -f
otelcol-contrib.service (upstream OTel release)
Section titled “otelcol-contrib.service (upstream OTel release)”Installed by the upstream .deb / .rpm from the OpenTelemetry release.
Reads /etc/otelcol-contrib/config.yaml.
sudo systemctl status otelcol-contrib
sudo journalctl -u otelcol-contrib -f
File locations cheat-sheet
Section titled “File locations cheat-sheet”| Path | Contents | Notes |
|---|---|---|
/usr/local/bin/linkmesh-agent |
Agent binary (optional onboarding agent) | Installed by the .deb / .rpm |
/etc/linkmesh/config.yaml |
Agent config (VM) | server.url + server.token; container path is /etc/linkmesh-agent/config.yaml |
/etc/alloy/config.alloy |
Alloy bootstrap (remotecfg + own_metrics) | Pipeline arrives via remotecfg, not from this file |
/etc/otelcol-contrib/config.yaml |
otelcol bootstrap (OpAMP) | Pipeline arrives via OpAMP, not from this file |
journalctl -u linkmesh-agent |
Agent logs | No flat-file logs by design |
journalctl -u alloy |
Alloy logs | Includes remotecfg poll lines |
journalctl -u otelcol-contrib |
otelcol logs | Includes OpAMP handshake lines |
LinkMesh server — public base URL (externalUrl)
Section titled “LinkMesh server — public base URL (externalUrl)”Everything above is the agent / collector host. The rest of this page is the
server’s own config, read from /etc/linkmesh/config.yaml on the host
running linkmesh-server (or from environment variables).
externalUrl is the first key to set on a new install — see
Quickstart step 2
for the walkthrough.
| Key | Type | Default | Notes |
|---|---|---|---|
externalUrl |
string (URL) | (empty) | The public base URL collectors and browsers reach this server on, e.g. https://linkmesh.example.com. No trailing path. Environment: LINKMESH_EXTERNALURL, or the shorter alias EXTERNAL_URL. |
What reads it:
- Collector self-telemetry (OpAMP). The server hands this URL to each OpAMP collector as the endpoint for its own_metrics push. This is what produces per-component throughput, CPU, memory and uptime on the topology canvas and collector detail pages.
- User invites and password resets. Both mail a link back into this
install and use
externalUrlas its base. - Enrollment scripts and download URLs shown in the UI.
- CORS. When
security.cors.allowedOriginsis empty,externalUrlbecomes the single allowed cross-origin. (The bundled UI is served same-origin, so this only affects external API callers.)
externalUrl: "https://linkmesh.example.com"
LinkMesh server — reverse proxy (http.trustedProxies)
Section titled “LinkMesh server — reverse proxy (http.trustedProxies)”| Key | Type | Default | Notes |
|---|---|---|---|
http.trustedProxies |
list of CIDRs | [] |
Addresses of the reverse proxies or ingress controller pods in front of the server. Only these peers may set X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host; from anyone else the headers are dropped. Empty means trust no one, which is correct when clients connect directly. Behind a proxy that is not listed, the audit log and the login throttle record the proxy’s IP for every request. The client IP is the rightmost X-Forwarded-For entry that is not itself a listed proxy; X-Real-IP is used only when there is no X-Forwarded-For, and True-Client-IP is ignored. A bare IP is read as a single address. Environment: LINKMESH_HTTP_TRUSTEDPROXIES, comma-separated. |
Example, nginx on the same host:
http:
trustedProxies: ["127.0.0.1/32"]
On Kubernetes, use the pod range your ingress controller runs in, for example
["10.48.0.0/16"]. See Run behind a reverse proxy.
LinkMesh server — storage & database
Section titled “LinkMesh server — storage & database”This section controls where the server keeps its operational state. See Storage backends for the concepts and Deploy with MongoDB for setup.
storage
Section titled “storage”| Key | Type | Default | Notes |
|---|---|---|---|
storage.backend |
bolt / mongodb |
bolt |
Embedded BoltDB database (default, single instance) or external MongoDB (required for high availability). |
storage.boltPath |
path | /data/linkmesh/state.db |
Where the embedded BoltDB file lives. Ignored when backend is mongodb. |
storage.auditLogRetentionDays |
int | 365 |
How long audit-log entries survive before the backend prunes them. Applies to both backends; 0 means the 365-day default. |
database — only when storage.backend: mongodb
Section titled “database — only when storage.backend: mongodb”Set database.uri directly, or set the parts and let the server build the
connection string. uri wins if both are present.
| Key | Type | Default | Notes |
|---|---|---|---|
database.uri |
connection string | (none) | Full MongoDB URI. Preferred for mongodb+srv:// (Atlas-style) strings. |
database.server |
host or host:port |
(none) | Used to build the URI when uri is unset. A bare hostname builds a mongodb+srv:// URI; host:port builds a plain mongodb:// one. |
database.user |
string | (none) | Username, folded into the built URI. |
database.password |
string | (none) | Password, folded into the built URI. Inject via env rather than committing it. |
database.database |
string | signalflow |
Database name. The default keeps the legacy name; set linkmesh on a fresh install. |
Embedded default — nothing to set:
storage:
backend: bolt
External MongoDB via a single URI:
storage:
backend: mongodb
database:
uri: "mongodb+srv://linkmesh-app:[email protected]/linkmesh?retryWrites=true&w=majority"
External MongoDB from parts (password injected via env):
storage:
backend: mongodb
database:
server: cluster.example.mongodb.net
user: linkmesh-app
database: linkmesh
Environment overrides
Section titled “Environment overrides”Every key takes a LINKMESH_-prefixed environment variable, with dots
flattened to underscores and the name upper-cased — handy for containers
and secret injection. A few database keys also accept shorter aliases:
| Config key | Environment variable |
|---|---|
storage.backend |
LINKMESH_STORAGE_BACKEND |
storage.boltPath |
LINKMESH_STORAGE_BOLTPATH |
storage.auditLogRetentionDays |
LINKMESH_STORAGE_AUDITLOGRETENTIONDAYS |
database.uri |
LINKMESH_DATABASE_URI — aliases DATABASE_URI, MONGODB_URI |
database.server |
LINKMESH_DATABASE_SERVER — alias MONGODB_SERVER |
database.user |
LINKMESH_DATABASE_USER — alias MONGODB_USER |
database.password |
LINKMESH_DATABASE_PASSWORD — alias MONGODB_PASSWORD |
LinkMesh server — authentication
Section titled “LinkMesh server — authentication”How operators sign in. auth.mode picks between the built-in user database
(local, the default) and an external OpenID Connect provider (external).
The walkthrough for the external path — registering a client, mapping claims,
keeping a break-glass account, and getting back in if the licence lapses — is
Configure OIDC single sign-on.
| Key | Type | Default | Notes |
|---|---|---|---|
auth.mode |
local / external |
local |
local = built-in users + password form. external = OpenID Connect, which requires an Enterprise licence (active or in grace). Without one the server still starts in external mode, logs a warning, and serves local password login only. Environment: AUTH_MODE. |
auth.tokenExpiry |
Go duration | 24h |
Lifetime of the LinkMesh session token, in both modes. Independent of the identity provider’s own session. Environment: AUTH_TOKEN_EXPIRY. |
auth.local.adminEmail |
string | admin |
The seeded administrator. In external mode this account is the break-glass login — it keeps working at POST /api/v1/auth/login while the login page shows only the sign-on button, and on every licence state (without a licence that covers single sign-on, the login page shows the password form again), so it is also the way back in when a licence lapses. Environment: AUTH_ADMIN_EMAIL. |
auth.local.adminPassword |
string | (auto-generated) | Initial password for the seeded administrator. Blank generates one and prints it to the startup log once. Environment: AUTH_ADMIN_PASSWORD. |
auth.passwordPolicy — local passwords
Section titled “auth.passwordPolicy — local passwords”Applies to every locally-set password: the seeded admin, admin-created users, invite redemption, self-service change and reset. External identities have no LinkMesh password and are unaffected.
| Key | Type | Default | Notes |
|---|---|---|---|
auth.passwordPolicy.minLength |
int | 12 |
Minimum length. |
auth.passwordPolicy.requireMixedCase |
bool | true |
Require upper and lower case. |
auth.passwordPolicy.requireDigit |
bool | true |
Require at least one digit. |
auth.passwordPolicy.requireSpecial |
bool | false |
Require at least one non-alphanumeric character. |
auth.passwordPolicy.checkBreachedHibp |
bool | false |
Reject passwords found in a public breach corpus. Requires outbound internet access from the server. |
auth.external — OpenID Connect
Section titled “auth.external — OpenID Connect”Read only when auth.mode: external, and used only while the server holds an
Enterprise licence. The server performs OIDC discovery
against the issuer at startup and takes the authorization endpoint, token
endpoint and signing keys (JWKS) from the discovery document — there is
nothing to configure for those. If discovery fails the server exits instead
of starting.
| Key | Type | Default | Notes |
|---|---|---|---|
auth.external.issuer |
string (URL) | (empty) | Required in external mode — the server refuses to start without it. The base URL whose /.well-known/openid-configuration returns the discovery document, e.g. https://idp.example.com/realms/company. Must be https://; plain http:// is accepted only for localhost / 127.0.0.1 / ::1. Environment: AUTH_EXTERNAL_ISSUER. |
auth.external.clientId |
string | (empty) | Required in practice — the client (application) registered for LinkMesh at the provider. Sign-in fails in the browser without it. Environment: AUTH_EXTERNAL_CLIENT_ID. |
auth.external.clientSecret |
string | (empty) | Required for a confidential client; leave empty for a public (PKCE-only) one. Used server-side in the code exchange and never sent to the browser. Inject via environment rather than committing it. Environment: AUTH_EXTERNAL_CLIENT_SECRET. |
auth.external.redirectUrl |
string (URL) | (empty) | Required — <your LinkMesh base URL>/auth/callback. Must match the redirect URI registered at the provider character for character. Also pins the callback endpoint: a code exchange naming a different redirect_uri is rejected. Environment: AUTH_EXTERNAL_REDIRECT_URL. |
auth.external.scope |
string | openid profile email |
Space-separated scopes requested at the provider. The email claim must end up in the ID token — LinkMesh keys user records on it and refuses a sign-in without one. Environment: AUTH_EXTERNAL_SCOPE. |
auth.external.defaultRole |
string | (empty) | Role granted when no claim mapping matches. Empty denies such users — the safe default, and the usual reason a correct provider setup still cannot sign anyone in. Environment: AUTH_EXTERNAL_DEFAULT_ROLE. |
auth.external.groupsClaim |
string | groups |
Claim path treated as the group list. Claim mappings carry their own claim path, so this only names the conventional default. Environment: AUTH_EXTERNAL_GROUPS_CLAIM. |
auth.external.requireApproval |
bool | false |
false provisions a LinkMesh user on first successful sign-in (just-in-time). true requires an administrator to create the user first; unknown addresses are refused even with a valid token and a matching claim. |
auth.external.authUrl |
string (URL) | (from discovery) | Override for the provider’s authorization endpoint. Leave unset unless the discovered endpoint is not reachable as advertised (split-horizon DNS). |
auth.external.tokenUrl |
string (URL) | (from discovery) | Override for the provider’s token endpoint. Same caveat. |
Claim-to-role mappings are not config-file settings — they live in the
database and are administered under Roles → claim mappings in the UI, or at
/api/v1/auth/claim-mappings. Each mapping carries a claim path (dotted paths
walk nested objects, e.g. account.roles), the value to match, the role to
grant, and a priority that breaks ties. See
Map identity-provider claims to roles.
jwt — session signing
Section titled “jwt — session signing”| Key | Type | Default | Notes |
|---|---|---|---|
jwt.secret |
string | (empty) | HS256 key the server signs its own session tokens with, in both auth modes. Set it — an install that never sets one cannot verify sessions across a restart. Environment: JWT_SECRET. |
jwt.secrets[] |
list | (empty) | Rotation keyset: several keys, each with id, value and active. Exactly one is active (it signs); the rest are accepted but not issued, so tokens signed before a rotation stay valid through the overlap. Takes precedence over jwt.secret when set. |
When in doubt
Section titled “When in doubt”The LinkMesh UI is the canonical place to change pipeline / source /
destination / route config. Bootstrap files on disk (config.alloy,
otelcol-contrib config.yaml) only carry the connection back to the server —
edits to pipeline-shaped blocks there will be overridden as soon as remotecfg
or OpAMP delivers the real config on the next poll/push.