Settings over MQTT
A server with a login database (logins.md) can also keep its
settings in that database, and you can read and change them over MQTT -
from the modbee config command, or from your own platform with nothing but
an admin login. Nobody needs a shell on the server or an editor on
modbee.yaml.
A change is stored at once and applied when the server restarts, and the
same request can ask for that restart. Logins are different: they change
while the broker runs (logins.md). Settings - ports, limits,
Kafka mappings - are what a broker is built from when it starts, so they wait
for the next start rather than half-apply to a running one.
The TLS certificate is the exception. Its certificate, key and client CA
are applied the moment they are stored, with no restart
(below), because a certificate is
renewed every few months and a broker a plant depends on should not stop for
it.
What it needs
authentication: database:- in the file, or as
MODBEE_AUTHENTICATION_DATABASE. The settings are kept in the same SQLite
file as the logins, in a table of their own. Thedocker/examplessetups
already have it.- The server started with a file (
modbee serve -C modbee.yaml). The
Docker image always is:/etc/modbee/modbee.yaml. - An admin login, as for managing logins.
Without a login database, modbee.yaml and the environment are all there is.
Two kinds of stored setting
| What | Changed with | |
|---|---|---|
| An override | One setting, named by its path - broker.port, broker.max-session-expiry, kafka.clusters[0].bootstrap-servers |
config.set |
| A document | A whole modbee.yaml, kept in the database in place of the file |
config.apply |
The path of a setting is its place in modbee.yaml, and it is exactly the
setting an environment variable names: broker.max-session-expiry is
MODBEE_BROKER_MAX_SESSION_EXPIRY. An override is therefore what a variable
is - a single value, or a list written comma-separated - and it can do what a
variable can do and no more:
- It changes a setting the file (or document) could hold. A setting
inside a section that is read only when it is written -metrics:,
broker: tls:- can be overridden once that section exists, not before. - It cannot add a Kafka cluster or mapping, or a section. That is a
change to the shape of the configuration, so it takes a whole document.
Which value wins
When the server starts, each setting comes from the first of these that has
it:
| 1 | A flag on the command line (modbee serve -C modbee.yaml -p 1884) |
| 2 | An environment variable (MODBEE_BROKER_PORT) |
| 3 | A stored override (config.set) |
| 4 | The stored document (config.apply) - or, when there is none, the file |
| 5 | The default |
The environment wins over the database. A setting a compose file pins
with a variable stays pinned, whatever is stored, and config.get lists
those variables as pinned so the reason is visible rather than guessed at.
Authentication is the file's and the environment's alone. A change over
MQTT that moved the login database, or changed the admin login, could lock
every admin out - and then nothing could change it back over MQTT. So
config.set refuses any authentication... path, and config.apply
refuses a document whose authentication: differs from what the file and
the environment give. That includes authentication.certificate-login
(logins.md): turned on, an admin
presenting a certificate is the login its CN names, so it is set with
MODBEE_AUTHENTICATION_CERTIFICATE_LOGIN=true or in the file - and
authentication.default-topics (MODBEE_AUTHENTICATION_DEFAULT_TOPICS). A document must therefore carry the file's
authentication: section as it is.
If the stored settings no longer load when the server starts - a newer
version that reads a setting differently, a file that has changed under
them - the server starts from the file alone, prints each problem, and says
so:
modbee serve: the stored settings would not load - starting from /etc/modbee/modbee.yaml alone
It does not refuse to start, because a broker that will not start over a
stored setting is one nobody can reach to fix it. config.get then lists the
same problems.
From the command line
modbee config uses the admin login, like modbee user. Inside a stack's
container it logs in by itself:
docker compose exec modbee modbee config get
settings from: file (/etc/modbee/modbee.yaml)
broker.max-session-expiry = 86400
pinned by the environment: MODBEE_AUTHENTICATION_USERNAME
pinned by the environment: MODBEE_AUTHENTICATION_PASSWORD
the running server is behind these settings - restart it to apply them
| Command | Does |
|---|---|
modbee config get |
Where the settings come from, every override, and what the environment pins. --json prints the whole answer, the document included |
modbee config set broker.queue=128 broker.max-session-expiry=86400 |
Store overrides |
modbee config set --remove broker.queue |
Drop an override, back to the file's value (--remove repeats) |
modbee config apply --file new.yaml |
Store a whole document in place of the file |
modbee config check --file new.yaml or modbee config check broker.port=1884 |
Would these load? Prints every problem, stores nothing |
modbee config reset |
Drop the stored document and every override: back to the file alone |
set, apply and reset take --restart: once the change is stored,
the server restarts to apply it (below).
A setting that does not exist, or a value the file would not accept either,
is refused with its problem, and nothing is stored:
docker compose exec modbee modbee config set broker.prot=1884
modbee: the settings were not stored: they would not load
broker.prot: no setting is called "broker.prot"
A setting ending -pem takes @file for its value - the PEM text of a file
on the computer modbee runs on - because nobody types a certificate:
docker compose exec modbee modbee config set broker.tls.cert-pem=@/tmp/broker.pem broker.tls.key-pem=@/tmp/broker.key
stored - and the TLS listener is serving it now
config get then shows each stored PEM as (PEM, n bytes), the stored key
as (set), and what TLS is serving:
tls: serving on 8883 (wolfSSL)
certificate: CN=mqtt.example.com, until 2027-01-05T10:00:00Z
sha256 3dd049baf3c5e7eaeaea212abd68147b15c891250bf4f82f2c2b1e4974412dfc
client certificates: required, from CN=Plant Devices CA
certificate login: on
--file is read by the modbee command, on the computer it runs on. For a
document on your own computer, run modbee there against the broker -
modbee config apply --file new.yaml -h mqtt.example.com --tls -u admin -P '...' -
or copy it into the container first:
docker compose cp new.yaml modbee:/tmp/new.yaml
docker compose exec modbee modbee config apply --file /tmp/new.yaml --restart
From your own platform
Every command is a JSON request, published to $modbee/admin/request with a
reply topic of $modbee/admin/<your client id>/reply subscribed - the same
channel as managing logins (logins.md).
command |
Fields | Answer |
|---|---|---|
config.get |
source (file or database), file, document, overrides, pinned, problems, restart_required, tls (below) |
|
config.check |
any of document, set, remove |
ok, and problems. Stores nothing |
config.set |
set: {"path": value, ...}; optional remove: [paths], restart |
problems, restart_required, restarting, tls_applied |
config.apply |
document: the YAML, as text; optional restart |
the same |
config.reset |
optional restart |
the same |
{"id": "7", "command": "config.set", "set": {"broker.port": 1884, "broker.max-session-expiry": 86400}}
{"problems": [], "restart_required": true, "restarting": false, "id": "7", "ok": true}
- A value is a string, a number,
true/false, or a list of those -
which is stored comma-separated, as a variable would hold it. problemslists each one as{"line", "path", "message", "warning"}.
lineis the line in the document, or 0 for an override. A warning does not
stop a change being stored.- Refused, the answer is
"ok": falsewith theproblemsand
"error": "the settings were not stored: they would not load". config.get'sdocumentis the stored document, or the file when there
is none, with everypassword:andkey-pem:value shown as(set); so
is an override whose path ends inpasswordorkey-pem. A private key is
never sent back, by any command. A document read back this way therefore
cannot be applied as it is - its passwords would be the text(set). Keep
secrets out of documents:password-file:, or the environment.restart_requiredis true while what is stored differs from what the
running server started with - leaving out the TLS material, which the
running server takes as it is stored.tls_appliedis true when the change gave the TLS listener new
material, which it is serving now.
The TLS material, applied while it runs
Four settings change what the TLS listener serves, and are applied to the
running server the moment they are stored - by config.set, config.apply
or config.reset alike:
| Path | |
|---|---|
broker.tls.cert-pem |
The certificate as PEM text - a full chain is presented whole |
broker.tls.key-pem |
Its private key, unencrypted PEM. Goes with cert-pem: one without the other is refused |
broker.tls.client-ca-pem |
One or more CA certificates. Set, client certificates are verified against it |
broker.tls.client-certificates |
required or optional (kafka.md) |
{"id": "8", "command": "config.set", "set": {
"broker.tls.cert-pem": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----\n",
"broker.tls.key-pem": "-----BEGIN PRIVATE KEY-----\nMIGH...\n-----END PRIVATE KEY-----\n"}}
{"problems": [], "restart_required": false, "restarting": false, "tls_applied": true, "id": "8", "ok": true}
- The text wins over the file's paths (
broker.tls.cert,key,
client-ca), as every stored setting wins over the file. - It is checked before it is stored. A certificate that will not parse, a
key that is not its key, a CA that is not a certificate: each is refused
with its problem while the old material is still being served. An expired
certificate is stored, with a warning saying so.config.checkasks the
same question and stores nothing. - New handshakes get the new material; connections already up keep
theirs. Nothing is disconnected, and every CPU core of the server
switches together. - With no
tls:section in the file, a certificate and its key stored
this way turn TLS on, onbroker.tls.port(8883 by default) - at the next
restart: turning TLS on or off, and its port and address, are what a
socket is bound to, so those still sayrestart_required. Ask for the
restart with"restart": true.
config.get's tls is what is being served now, never what is stored
for later, and never the key:
"tls": {
"serving": true,
"port": 8883,
"engine": "wolfSSL",
"certificate": {
"fingerprint": "3dd049baf3c5e7eaeaea212abd68147b15c891250bf4f82f2c2b1e4974412dfc",
"subject": "CN=mqtt.example.com",
"not_after": "2027-01-05T10:00:00Z",
"names": ["DNS:mqtt.example.com", "IP:203.0.113.7"]
},
"key": "set",
"client_ca": {"fingerprint": "1aa71917...", "subject": "CN=Plant Devices CA", "certificates": 1},
"client_certificates": "required",
"certificate_login": true
}
| Field | |
|---|---|
serving |
Whether there is a TLS listener. False: every other field is empty (certificate and client_ca are null) |
certificate.fingerprint |
SHA-256 of the certificate's DER: 64 lowercase hexadecimal characters - openssl x509 -noout -fingerprint -sha256, lowered, without its colons |
certificate.subject |
The subject, in the certificate's own order |
certificate.not_after |
When it expires, ISO 8601 UTC |
certificate.names |
Its subject alternative names: DNS:, IP:, email:, URI: |
client_ca |
The first CA certificate's fingerprint and subject, and how many certificates the bundle holds; null without one |
client_certificates |
required, optional, or none without a client CA |
certificate_login |
Whether a verified certificate logs in by itself (logins.md) |
A password set as an override is stored in the database as written - unlike a
login's secret, a setting has to be readable to be used - and goes into every
backup of it. password-file: keeps it out.
Applying a change: the restart
A change applies when the server next starts. --restart (or
"restart": true) asks for that start now: once the answer has been sent,
the server stops gracefully, exactly as docker stop stops it - devices
are told the server is shutting down, and messages on their way to Kafka are
given their time.
What brings it back is whatever runs it:
- Docker, with a restart policy of
unless-stopped(every compose file
here has it) oralways. A graceful stop exits with code 0, soon-failuredoes not
bring it back. - systemd, with
Restart=always. - Nothing, when the server was started by hand: it stays stopped.
"restart": true restarts only when it is needed: if what is stored is
already what the server is running, the answer has "restarting": false and
nothing stops.
Backups
The settings are in the login database, so modbee backup and the daily copy
(logins.md) hold them, and modbee restore puts them
back with the logins.