Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

mtui is configured with a sectioned TOML file. All configuration is optional: with no file present, mtui runs on built-in defaults.

File resolution

The config file is resolved in this order:

  1. --config <file> — an explicit path. Short-circuits the search.

  2. $MTUI_CONF — an explicit path in the environment. Short-circuits the search.

  3. Otherwise the default pair is merged, lowest precedence first:

    • /etc/mtui.toml (system-wide)
    • ~/.mtui.toml (home dotfile)
    • $XDG_CONFIG_HOME/mtui/mtui.toml (per-user XDG; falls back to ~/.config/mtui/mtui.toml)

    When several of these exist they are merged: an option set in a higher-precedence file overrides the same option in a lower one. The per-user XDG file therefore wins over /etc on shared keys.

Loading is lenient: a missing or malformed file — or a single bad value — is logged at ERROR and skipped, falling back to the default. A bad value never invalidates the rest of the file, and loading never hard-fails.

~ is expanded to your home directory in path-valued options.

Inspecting and changing config at runtime

The config command shows the resolved configuration and can set individual options for the running session:

config show                 # print all resolved options
config show connection_timeout
config set max_parallel 8

Secret options are never echoed: config show/config set print <set> for a configured secret (currently gitea_token) instead of its value. Over MCP, config show additionally refuses the bulk dump and operator-local values; see MCP server.

Options

Defaults below are the built-in values.

[mtui]

KeyTypeDefaultMeaning
template_dirpath$TEMPLATE_DIR or .Directory holding each loaded report’s working directory (install logs, result files).
userstringcurrent login userUser attributed to this session (locks, logs).
install_logsrelative dir nameinstall_logsSub-directory (single relative name, no separators) where install logs are written per update.
ssl_verifybool / stringtrueTLS verification for outbound HTTP: a boolean, a boolean spelling (yes/no/on/off/…), or a path to a custom CA bundle.

[connection]

KeyTypeDefaultMeaning
connection_timeoutseconds (>0)300SSH per-command no-output timeout. The SSH link is kept alive, so this window, not a transport idle limit, decides when a quiet command is aborted. Does not bound the connect handshake — see connect_timeout.
connect_timeoutseconds (>0)60SSH connect handshake budget (TCP connect, banner, and auth). Also bounds each SFTP request (lock/history file operations), which otherwise defaults to a fixed 10s. Raise it for a host with a genuinely slow banner or link (e.g. a loaded s390x LPAR, a jump-path host, or a WAN/VPN refhost); a host slower than this to respond is dropped from a batch with a warning instead of eventually connecting.
reboot_timeoutseconds (>0)10Backoff base for reconnecting after a reboot (prepare/reboot/update). Sleeps grow as 2*(reboot_timeout + 5*count) across attempts.
reboot_retriesint (>0)10Number of post-reboot reconnect attempts beyond the first. With the defaults this gives a rebooting host up to ~12.7 minutes to come back.
max_parallelint (>0)50Max hosts to fan out to concurrently (SSH/SFTP/lock/connect batches).
max_oqa_parallelint (>0)8Max concurrent openQA/QAM HTTP requests in the overview search (kept low to be polite to shared hosts).
ssh_strict_host_key_checkingstringauto_addSSH host-key checking policy (auto_add, strict, warn, …).

SSH is pubkey-only by design. mtui authenticates from the SSH agent or ~/.ssh/id_*; there is no password auth.

reboot_timeout/reboot_retries only apply to the post-reboot reconnect budget. A dead SSH link mid-run/shell, or the sftp pre-op check, still fails on the first probe — those are not recovering from a reboot.

[refhosts]

KeyTypeDefaultMeaning
resolversstringhttps,pathComma-separated ordered list of refhosts resolvers.
https_uriURLhttps://qam.suse.de/refhosts/refhosts.ymlHTTPS URI of the refhosts database.
https_expirationseconds (>0)43200 (12h)Age before a cached HTTPS fetch is considered stale.
pathpath~/.local/share/refdb/refhosts.ymlLocal filesystem refhosts database (also used as the HTTPS resolver’s on-disk mirror; shared with the refdb tooling).

[url]

KeyTypeDefaultMeaning
bugzillaURLhttps://bugzilla.suse.comBugzilla base URL.
testreportsURLhttps://qam.suse.de/testreportsTestreports base URL.
fancy_reportsURLhttps://qam.suse.de/reports“Fancy” reports base URL.

[target]

KeyTypeDefaultMeaning
tempdirpath/tmpRemote scratch directory on target hosts.

[qem_dashboard]

KeyTypeDefaultMeaning
apiURLhttp://dashboard.qam.suse.de/apiQEM Dashboard API base URL.

[teregen]

KeyTypeDefaultMeaning
apiURLhttps://qam.suse.de/api/v1TeReGen report/queue API base URL.
api_v2URLhttps://qam.suse.de/api/v2TeReGen v2 report/queue API base URL (the JSON-document read path).

[openqa]

KeyTypeDefaultMeaning
openqaURLhttps://openqa.suse.deopenQA instance URL.
baremetalURLhttp://openqa.qam.suse.czBaremetal openQA instance URL.
distristringsleopenQA install distri parameter.

openQA API credentials (key/secret, used to sign requests with X-API-Hash) are not part of mtui.toml. mtui delegates their discovery to the ruoqa client, which resolves them in this order:

  1. $OPENQA_API_KEY/$OPENQA_API_SECRET, honoured as a pair — setting only one is a configuration error. When both are set they are used for every instance, ahead of client.conf.
  2. The openQA project’s own client.conf — an INI file with one [host[:port]] section per instance — found via a tiered, non-merging search: $OPENQA_CONFIG (when set), else the user config directory ($XDG_CONFIG_HOME/openqa, or ~/.config/openqa when $XDG_CONFIG_HOME is unset), else /etc/openqa and /usr/etc/openqa. The first tier that yields any file wins outright; later tiers are not read at all — a ~/.config/openqa/client.conf does not merge with sections that exist only in /etc/openqa/client.conf; it replaces it. Within the winning tier, each directory contributes its client.conf (if present) followed by its sorted client.conf.d/*.conf drop-ins, later files overriding earlier keys.

An instance with no matching section (or no client.conf at all, and no $OPENQA_API_KEY/$OPENQA_API_SECRET pair) is queried unauthenticated, which openQA permits for GET requests.

[gitea]

KeyTypeDefaultMeaning
tokenstring (secret)(empty)API token for the Gitea PR review workflow. The Gitea connector refuses to build without it. Masked as <set> in config output; sent only in an Authorization header, never logged.
urlURLhttps://src.suse.deTrusted Gitea origin the token may be sent to. A PR API URL comes from checked-out metadata (which is not fully trusted), so the token is attached only to requests whose origin (scheme/host/port) matches this value, over https, with no embedded userinfo. Point it at another instance (e.g. https://src.opensuse.org) to use that one. A metadata URL on any other origin is refused before the token is sent.

[slack]

The Slack review-request integration behind the request_review command.

Off by default. Posting into a chat workspace is an outward-facing side effect, so it never happens implicitly: enabled must be true and both token and channel must be set. An mtui with no [slack] section never contacts Slack, and request_review refuses with the reason.

Enabling it also gates approve. Once enabled = true, an update can only be approved after its Slack review request has been acknowledged. approve refuses unless all three hold:

  1. request_review recorded a marker for the update (and committed it, so a reviewer approving from a different checkout sees it);
  2. the marked Slack message still names that RRID — a marker copied between templates cannot launder an approval;
  3. somebody other than the bot left an approving reaction (👍, ✅; skin tone ignored).

If Slack cannot be read, approve fails closed — an unreadable review request is not an approved one. There is deliberately no per-command bypass flag: a gate with one is not a gate. Turning it off is a config change (config set slack_enabled false), which is explicit and auditable. reject is never gated — blocking it would strand an update a reviewer wants stopped.

KeyTypeDefaultMeaning
enabledboolfalseWhether the integration is available at all. Set true to opt in; leaving it unset (or false) makes request_review refuse up front, which is how a site that does not use Slack — or wants the feature switched off despite credentials being present — runs mtui.
tokenstring (secret)(empty)Slack bot token. Required scopes: chat:write, reactions:read, channels:history. Masked as <set> in config output; sent only in an Authorization header, never logged.
channelstring(empty)Channel review requests are posted to — an ID (C0123456789) or a #name. Overridable per call with request_review --channel.
api_urlURLhttps://slack.com/apiSlack API base the token may be sent to. Refused unless it is https (or http to loopback, which is what makes the test suite’s mock server possible) and carries no userinfo.
poll_intervalint (seconds)120How often request_review --watch polls for reactions. Jittered by ±15% so several mtui instances watching the same channel do not synchronise. Slack’s tier-3 methods allow roughly 50 requests/minute.
watch_timeoutint (seconds)3600How long request_review --watch runs before giving up.

Example:

[slack]
enabled = true
token = "xoxb-…"
channel = "#qam-review"

[lock]

Remote-lock behaviour on target hosts, so concurrent testers can share a fleet.

KeyTypeDefaultMeaning
reap_stalebooltrueOn connect, force-remove a pre-existing operation lock older than stale_age, regardless of owner.
stale_ageseconds86400Age beyond which an operation lock is stale and reapable. 0 disables reaping.
pool_reap_stalebooltrueOn a pool claim attempt, force-remove a pre-existing pool claim older than pool_stale_age, regardless of owner. The only automatic recovery for a claim orphaned by an uncatchable exit (SIGKILL/panic/power loss).
pool_stale_ageseconds86400Age beyond which a pool claim is stale and reapable. 0 disables pool-claim reaping.
pi_autolockbooltrueWhen testing a Product Increment (PI), lock each refhost as it connects while the report is loaded, released on unload/quit.
waitseconds0Pool-claim queueing budget. 0 fails fast on a busy host.
wait_pollseconds (>0)15Poll interval while waiting for a busy pool lock to free.

[mcp]

mtui-mcp server behaviour. See MCP server.

KeyTypeDefaultMeaning
max_output_bytesbytes100000Upper bound on a single tool result (truncated at the tail with a notice). 0 disables the cap.
max_input_bytesbytes10000000Per-host source cap on data a tool reads before returning it (testreport reads, get downloads; put payloads above it are refused). 0 disables.
max_request_bytesbytes10000000Upper bound on an inbound HTTP request body under --transport http (oversized requests are rejected with 413 before rmcp buffers them). Also sets rmcp’s own request-body ceiling (default 4 MB), so a single knob governs both gates. 0 disables mtui’s limit entirely, dropping even axum’s implicit 2 MB floor, and raises rmcp’s gate to unlimited.
max_active_jobsint16Ceiling on concurrent background jobs per session. 0 disables.
max_completed_jobsint128Ceiling on retained terminal job records per session (FIFO eviction). 0 disables.
session_capint (>0)32Ceiling on concurrent per-client sessions under --transport http.
session_idle_timeoutseconds (>0)14400Inactivity before an idle http session is swept. Also pins rmcp’s streamable-HTTP keep-alive (rmcp’s own default is 300 s), so the transport never tears a quiet session down before this sweeper would.
sweep_parallelint (>0)4Max stale sessions the idle sweeper tears down concurrently per cycle.
profilestringfullTool-surface profile: full (every synthesised tool) or core (curated everyday subset). Unknown → full with a warning. Agent clients should prefer core (36 vs 65 tools on every request); see MCP server.
tools_allowarray of strings(empty)Extra tool names to keep on top of the profile.
tools_denyarray of strings(empty)Tool names to remove regardless of profile/allow (deny wins last). E.g. ["updates", "openqa_overview"] for a workflow that never lists the queue or polls openQA.
audit_logpath(unset)Durable audit sink for tool calls (versioned JSONL, 0600): one or two records per call — an intent before and an outcome after for a tool without readOnlyHint, an outcome alone for a read-only one — plus a terminal record per background job. Unset disables auditing. A path that cannot be opened refuses to start the server; afterwards a failed pre-dispatch intent write refuses the call (nothing ran), while a failed outcome write returns the result with an in-band notice. See MCP server.
audit_log_max_bytesbytes268435456Size at which audit_log rotates to <path>.1, keeping five generations (.1….5); the oldest is discarded, so archiving beyond it is yours to arrange. Rotation never blocks the dispatch worker and never refuses a call: it takes the file’s advisory lock without waiting, and a lock it cannot take or a rename it cannot perform warns once and keeps appending past the cap. 0 disables rotation entirely; any other value below 4096 is raised to it with a warning.

OTLP export of the same record needs no TOML key by design: it is env-only (OTEL_EXPORTER_OTLP_ENDPOINT / ..._LOGS_ENDPOINT, ..._HEADERS / ..._LOGS_HEADERS, ..._PROTOCOL / ..._LOGS_PROTOCOL, OTEL_SERVICE_NAME). Endpoint set with audit_log unset is OTLP-only; neither is auditing off. See MCP server.

The profile key is profile, not tool_profile — it already sits under the tool-scoped [mcp] table. tools_allow/tools_deny are native TOML arrays, not comma-separated strings. (Both matter if you are carrying over a config from a pre-26.0 release.)

[obs]

The native OBS/IBS QAM review backend.

KeyTypeDefaultMeaning
api_urlURLhttps://api.suse.deOBS/IBS API the review backend acts against. Must equal a section header in your oscrc.
request_timeoutseconds (>0)180Coarse wall-clock budget for a whole native OBS operation, checked between HTTP calls (not a mid-call hard kill).

Credentials for OBS live only in your oscrc, not here. The oscrc is located like osc itself: $OSC_CONFIG → $XDG_CONFIG_HOME/osc/oscrc → ~/.oscrc. There is no mtui-side path option — set $OSC_CONFIG to point at a non-default oscrc.

Secrets and credentials

  • The gitea_token is masked as <set> by both config show and config set, and is never written to logs or display output.
  • Configured datasource URLs may embed credentials (scheme://user:pass@host). mtui sanitises the userinfo to *** before any URL reaches a log or an error message, keeping the host visible for diagnosis.
  • A repository URL from the report metadata may carry userinfo (https://user:token@host/repo/) and is sent to zypper unchanged, so authenticated repositories keep working. Everything mtui records about the command — log lines, the timeout prompt, errors, and the command and output kept in the per-host log that showlog and export print — masks the userinfo to ***, including a URL a remote tool echoes back in its output. A password that appears without a scheme://…@ URL around it is not recognised.

Example

[connection]
connection_timeout = 300
connect_timeout = 60
reboot_timeout = 10
reboot_retries = 10
max_parallel = 8

[refhosts]
resolvers = "https,path"

[obs]
api_url = "https://api.suse.de"

[mcp]
profile = "core"
tools_deny = ["put", "get"]