[!WARNING] Not a supported Solace product.
solace-utilwas created by Solace Professional Services and is supported only by Solace Professional Services – not by Solace Support. For help with this tool, contact your Solace Professional Services representative rather than opening a Solace Support case. This notice covers this tool only, not the Solace PubSub+ Event Broker or the EventBroker Operator that it deploys and operates.
Every solace-util command reads one YAML env file. This document explains how that file is
found, how it decides which platform runs, and what every commonly-used key means.
Do not assemble a file from the tables below – start from one the binary writes:
solace-util examples --platform kubernetes -o env/dev.yaml # or docker, or podman
solace-util examples # every key, annotated
The three platform names give a minimal standalone file: only the keys that platform
cannot default, declaring only its own section. full is the fully annotated schema –
every key the loader accepts and the default each omitted one takes – and is the same
text as env/sample.yaml, which is generated from it. This document
explains the keys; those are the things you start from.
Every command reads one YAML env file, selected with -e/--env. The value is an actual
file name, taken literally – no extension is ever inferred, so -e dev and
-e dev.yaml name different files:
<base-dir>/env – so
-e dev.yaml finds ./dev.yaml if it exists, otherwise ./env/dev.yaml. The first hit
wins, which means a copy in the base directory shadows the env/ copy of the same
name. Every run echoes the file it resolved to (==> env file: ..., on stderr), so the
winner is never a surprise.--base-dir replaces the current directory for both lookups (default: current directory).env/ or joined with --base-dir – e.g. -e ./configs/prod.yaml,
-e ../shared/prod.yaml, or an absolute path.env.yaml, so a bare solace-util validate looks for
./env.yaml then ./env/env.yaml. Neither is shipped; write one with
solace-util examples --platform <platform> -o env.yaml.When no candidate exists the error names every path that was tried.
Decoding is strict: an unknown or misspelled key is a hard error, so typos fail loud
instead of being silently ignored. A file that is not YAML at all is reported as such –
and if it looks like a legacy bash env file, the error points at solace-util convert
(below).
Every host path in the file – tls.cert, tls.certKey, tls.cas,
broker.cliScriptsDir, broker.hostDiagnosticDir, broker.domainCerts.dirs[].path,
broker.domainCerts.files{}, docker.composeFile – is resolved against the directory
the env file itself was found in, never against the directory the command was run from.
So /srv/solace/env/prod.yaml declaring tls.cert: certs/tls.crt means
/srv/solace/env/certs/tls.crt, whichever directory you drive it from. An absolute value
is left exactly as written.
The reason is not tidiness. A podman Volume= source with no leading separator is read
by podman as the name of a named volume, so a relative certificate path made podman
create an empty volume and mount it over the real certificate – no error, unit starts,
broker starts, and TLS is not what you configured. systemd gives a unit no useful working
directory, so there was never a current directory that would have made the relative form
work.
Three fields deliberately do not follow the rule:
| Field | Why |
|---|---|
<platform>.container.dataDir |
Required absolute instead. It is the host side of a bind mount and what broker remove --delete-data empties (the directory itself stays), so quietly changing which directory that points at is not a fix. The default /opt/solace/data already satisfies it |
podman.quadletDir |
Checked, never resolved. The unit must live where systemd scans, so resolving a relative value would invent a location systemd never reads. Both defaults are already absolute |
podman.baseDir |
Optional, and required absolute when set. Deploy and remove delete a legacy private-key file under it, so resolving a relative value would aim that delete at a directory you never named |
Host paths are also character-checked, and the rule is deliberately looser than the one
for the command fields: a path may carry a
backslash and a colon, because C:\certs\tls.crt is a real path and a command token has no
business carrying either. (A tilde is not part of that difference: both sides admit an
embedded one – an 8.3 short name such as C:\Users\RUNNER~1\... – and both expand a leading
one, see below.) What
a path may not carry is a $ or a shell metacharacter (both container artifacts write
these values verbatim, and compose interpolates $ across the whole document), whitespace
(a mount is written source:target:options on one line, so a space cannot be delimited), or
a control or invisible character. The check runs on the value as you wrote it, before any
resolution.
A leading ~ expands to your own home directory, in every path key and in every command
field. ~, or ~/..., or ~\..., expands to os.UserHomeDir() – the home directory of
whoever is running this tool – before the relative-path rule above ever sees it, so the
expanded value is already absolute and is never joined onto the env file’s directory. A tilde
anywhere else in the value is left alone, which is what keeps an 8.3 short name like
C:\Users\RUNNER~1\... working. ~someoneelse/... (another user’s home) is not supported and
is refused by name rather than guessed at. An unresolvable home directory is an error naming
the field, and so is a home directory whose own path carries whitespace or a character the
value’s destination cannot carry.
There are no exceptions among the path keys – the three in the table above included. Those
three are still never resolved against the env file’s directory, so a relative value is
still refused; a ~ one is not relative by the time the check runs. What each is checked for
does not change:
podman:
quadletDir: ~/.config/containers/systemd
baseDir: ~/solace
container:
dataDir: ~/solace/data
In a command field every token expands except the first, which names the binary and must
stay a bare allowlisted name (kubectl, oc, docker, podman, …) with no path in it at
all – see the command fields. So this works:
kubernetes:
command: oc --kubeconfig ~/solace/kubecontext
and command: ~/bin/oc ... is refused, naming argv[0] as the reason.
A ~ value is the one thing in this schema that does not travel. Every other host path
either is absolute or resolves against the env file’s own directory, so the same file
describes the same deployment on any machine; a ~ resolves against whoever runs the
tool, so an env file carrying one means something different for each operator – and if their
home cannot be resolved, or carries a space, it means a failed load. Write it out
in full in any file you share.
The command tree is flat and identical on every platform – there is no kubernetes,
docker or podman subtree to type. The platform is a property of the deployment the env
file already describes, so it is resolved from that file rather than repeated on the
command line. Resolution happens in this order:
--platform <name>, if given. It accepts the canonical names kubernetes, docker,
podman, or the abbreviations kube, dk, pm. Neither k8s nor k8 is accepted, as
an abbreviation or as a section name – the platform goes by the product’s own word. It
must name a platform section the env file actually declares – passing --platform docker
against a file with no docker: section fails loudly rather than running against a
platform the file never described.kubernetes:,
docker:, and podman: sections it declares:
==> platform: docker (from ./dev.yaml))--platformAn env file must declare its platform section even when every setting under it
defaults – write docker: {} rather than leaving the section out. This matters because
docker and podman have no mandatory field of their own, so without the marker the file
would be indistinguishable from a kubernetes one that simply hasn’t set any docker keys.
The tree itself is the union of every platform’s commands, and it renders the same way
regardless of which platform an env file names – --help and shell completion never load
one, so a command cannot appear or disappear depending on a file they have not read. A
command that does not apply to the platform a file resolves to says so in its help text
(for example “(kubernetes only)”) and refuses at run time with a named error rather than
silently doing nothing.
Minimum required (Kubernetes):
| Key | Purpose |
|---|---|
image.repo |
Broker image repository: a lowercase path, optionally led by a registry host (solace/solace-pubsub-standard, localhost:5000/solace/broker) |
image.tag |
Image tag: 1-128 letters, digits, _, . or -, not starting with . or -. A digest is not supported. Written quoted into the CR, so 10.10 stays a string rather than becoming the number 10.1 |
kubernetes.name |
Broker / custom-resource name |
kubernetes.namespace |
Target namespace |
kubernetes.storage.msgNodeSize |
Message-node PVC size, a Kubernetes quantity (30Gi; no sign or exponent). Mandatory unless customVolumeMount covers every node, which leaves nothing to provision |
kubernetes.storage.customVolumeMount.<primary\|backup\|monitor> |
Mount an EXISTING PersistentVolumeClaim for that node instead of provisioning one. Mutually exclusive with kubernetes.storage.class – naming both is refused, since the CRD does not say which wins. All nodes in the redundancy group or none. broker remove --delete-data never deletes these: the volume may hold data that predates this broker, so it is reported and left for you |
semp.adminPass is not required on Kubernetes, unlike docker and podman: without it the CR
references kubernetes.adminSecret, or names no admin Secret and lets the operator generate
one – see The admin Secret. The admin username is always admin, the
broker’s own name for the built-in account, and there is no key to change it.
Common optional knobs:
| Key | Default | Purpose |
|---|---|---|
redundancy.<primary\|backup\|monitor>.name |
this host’s hostname (standalone containers only) | The broker’s routername, and the container’s hostname. On docker/podman in HA all three are mandatory: each is the key of that node’s entry in the group table every host renders (redundancy_group_node_<name>_connectvia), and a host knows its own name and no other machine’s, so one it filled in itself would build a table the other two disagree with. In standalone it is optional – there is one node, it is always this host, so the host’s own OS hostname is used when the key is omitted, and the run says so. Not read on Kubernetes, where the operator names the pods after kubernetes.name |
redundancy.enabled |
false |
true = HA group (primary+backup+monitor); false = single standalone broker. HA provisions three brokers, so it must be asked for explicitly. The group’s members live under redundancy.<primary\|backup\|monitor>.<name\|addr> and the key they authenticate with under redundancy.psk |
replication.* |
– | A DR pair: two SEPARATE brokers, each message-VPN active at one site and standby at the other. Not redundancy, which is the three nodes of one HA group – a replicated deployment usually has both, an HA group at each site. Omit the section entirely unless this broker replicates; see Replication |
image.registry |
docker.io | Registry prefix for the image reference: a host with an optional :port and path (registry.example.com:5000, ghcr.io/solace). All three image.* parts are held to the image-reference grammar at load, since each is written as-is into the quadlet unit, the compose file and the CR, where a newline or a space would add keys of its own |
kubernetes.storage.class |
cluster default | StorageClass for the broker PVCs |
kubernetes.storage.monNodeSize |
5Gi |
Monitor-node PVC size, a Kubernetes quantity like msgNodeSize. The monitor holds no message spool, so it needs far less than msgNodeSize |
kubernetes.serviceAccount |
none | ServiceAccount the broker pods run as. Unset lets the operator use the namespace default |
kubernetes.updateStrategy |
automatedRolling |
automatedRolling or manualPodRestart |
kubernetes.command |
kubectl |
Cluster CLI (legacy KUBE). A scalar is split on whitespace, so it can be a drop-in (oc) or a profile (kubectl --kubeconfig <file>). Restricted – see The command fields are executable content |
docker.command / podman.command |
docker / podman |
Container CLI (legacy CONTAINER_RUNTIME), same forms and the same restrictions as kubernetes.command |
docker.compose |
<command> compose |
The compose invocation. Set it to docker-compose on a host carrying only the standalone v1 binary; same forms and restrictions as docker.command, plus the one permitted compose subcommand |
<docker\|podman>.container.healthCheck.enabled |
false |
Adds an engine health check polling the broker’s own /health-check/readiness on port 5550 every 5s, so docker ps and podman’s auto-restart see readiness rather than liveness. Needs broker 10.26 or later and a version-numbered image.tag; set healthCheck.cmd to supply your own probe instead (which skips the version check). A custom probe runs inside the container under no-new-privileges, so it cannot use su, sudo or a setuid helper – see What the broker cannot gain. Its interval, timeout and startPeriod are Go durations (5s, 1m30s): startPeriod may be 0s, the other two must be above zero. Container-only by design – on Kubernetes the operator already probes the pods |
kubernetes.tlsServerSecret |
– | Name of the TLS Secret the broker uses; a Secret to reference – named here, or derived – is what enables the CR’s TLS block. Optional: unset derives <kubernetes.name>-tls when tls.cert/tls.certKey are set, and names nothing (no tls block) when they are not – the same three states as kubernetes.imagePullSecret. Lives under kubernetes.* because it names a Kubernetes Secret object – the cert/key files themselves stay platform-neutral under tls.*. Naming it does not mean building it: with tls.cert/tls.certKey set, this tool builds the Secret and removes it on teardown; without them the Secret must already exist and is only referenced – see Bring your own TLS Secret |
tls.cert / tls.certKey |
– | The server certificate and its private key, as two separate host files. Inseparable on every platform: setting one without the other is refused at load, in both directions. On docker and podman the broker reads the certificate as ONE file containing the key, the certificate, then the tls.cas chain, and this tool builds that file from them. Kubernetes takes them as two keys in a Secret and lets the operator assemble them – a Secret carrying only tls.crt is one the broker cannot start a listener over |
tls.cas |
– | The CA chain the broker presents after its server certificate – intermediates first, in the order listed. Part of the server certificate on every platform: appended to the docker/podman secret, to the Kubernetes TLS Secret’s tls.crt, and to what broker configure server-certs loads, and covered by the certificate’s digest, so a renewed CA is noticed like a renewed certificate. Not trusted CAs: those are broker.domainCerts, which broker configure domain-certs loads |
<docker\|podman>.container.name |
solace |
The container’s name, and the stem of every derived name (the podman unit and service, the host-side secret names). Held to the engines’ own grammar: it must start with a letter or digit, then letters, digits, ., _ or -. A name that YAML would read as a boolean or number (yes, off, 0123) is legal here and quoted in the generated compose file, so it stays the string you wrote. On docker it is also the compose project name, lowercased with anything outside [a-z0-9_-] folded to -, since compose’s grammar is narrower than the engines’ – override with COMPOSE_PROJECT_NAME (operations.md) |
podman.rootless |
false |
Which podman this file deploys. true = the unit under $XDG_CONFIG_HOME/containers/systemd loaded by systemctl --user, container.runUser defaulting to 1000:0, and ownership applied through podman unshare. Declared, then checked against the account you run as: every podman command except generate refuses when they disagree – true under sudo, or false without it – so a mistyped sudo is a refusal rather than a second, rootful deployment. generate is exempt because one file must render one artifact from any account. See Rootless podman prerequisites |
podman.baseDir |
– | Optional and unused; absolute when set. Earlier builds wrote the server-certificate bundle (private key included) here as a host file; the certificate now rides podman’s secret store like every other credential, so nothing is written here. When set, broker deploy and broker remove delete the old <container.name>-tls-servercertificate.pem in it, so an upgraded host is not left holding the key. Leave it set until every host has been redeployed once, then remove it |
kubernetes.imagePullSecret |
– | Name of the image-pull Secret the CR references. Optional: unset derives <kubernetes.name>-image-pull when registry credentials are configured, and names nothing (no pullSecrets block) when they are not. Naming it does not mean building it, the same rule kubernetes.tlsServerSecret follows above. With image.user/image.pass set (or their *Env equivalents), this tool builds the Secret, applies it alongside the operator’s fixed-name regcred, and removes both on teardown. Without them, a configured name must already exist – created by hand or by a cluster admin – and is only referenced, never built or deleted. The credentials themselves stay under image.*; docker and podman use them for <command> login, having no operator and no regcred |
kubernetes.imagePullPolicy |
– | Always | IfNotPresent | Never; unset keeps the CR’s own IfNotPresent |
kubernetes.adminSecret |
– | Name of the Secret holding the broker’s admin credentials, which the CR’s adminCredentialsSecret references. Optional: unset derives <kubernetes.name>-admin when semp.adminPass is set, and names nothing when it is not – the operator then generates <kubernetes.name>-pubsubplus-admin-creds. Naming it does not mean building it, the rule the TLS and image-pull Secrets follow. See The admin Secret |
kubernetes.operator.namespace |
pubsubplus-operator-system |
Namespace the cluster-scoped EventBroker Operator is installed to and addressed in. Two rules, neither of which asks the cluster: use this when set, otherwise the fixed default operator deploy installs to. So every operator command resolves the SAME namespace, and this tool never searches a cluster to find one – an unanchored name match could resolve to another team’s operator. Stays optional; most deployments never set it |
kubernetes.operator.image |
solace/pubsubplus-eventbroker-operator:1.4.2 |
The operator image operator deploy installs, with image.registry prefixed when set: a lowercase repository path with an optional :tag and @sha256: digest |
kubernetes.operator.watchNamespaces |
"" |
Comma-separated namespaces the operator watches, each a DNS-1123 label. operator deploy unions it with what the installed operator already watches, and adds kubernetes.namespace unless watchBrokerNs: false; false with an empty list watches every namespace |
kubernetes.operator.cpu / mem |
500m / 512Mi |
The operator container’s limits, Kubernetes quantities (no sign or exponent). These four operator keys are substituted into the operator bundle as-is, so each is checked at load |
semp.additionalUsers |
– | Extra CLI (management) users, each {username, accessLevel, password\|passwordEnv} with accessLevel one of none, read-only, mesh-manager, read-write, admin. Created at boot on every platform. The username must start with a letter or _ and be 1-32 characters (the broker’s own rule); on Kubernetes it may not contain . or - either, because the credentials ride the pod environment there and the kubelet drops variables whose names are not identifiers. See Extra CLI users differ by platform |
semp.adminPassEnv (and every other *Env) |
– | Name of an environment variable holding the secret, instead of the value itself. See Secrets |
timezone |
– | Broker timezone, all platforms (the CR’s timezone and the containers’ TZ). Omitted keeps the image default |
broker.cliScriptsDir |
cli |
Host folder broker perform cli-script and shell-script read scripts from – the only place they look. A script is named by its bare file name and must exist here; a path is refused and the current directory is never searched. Relative to the env file’s directory, so for env/dev.yaml the default is env/cli |
broker.hostDiagnosticDir |
diag-configs |
Host folder broker perform gather-diagnostics writes its bundle to |
broker.productKeys |
– | The Solace licence keys broker configure product-keys applies. Strings, not paths |
broker.domainCerts |
– | CA certificates broker configure domain-certs loads: dirs, a list of directories walked one level deep, plus files, explicit CA-NAME: full host path entries. Neither is defaulted, so an env file configuring neither is a no-op. A files key is checked at LOAD against the same charset and 64-character cap a directory-derived name meets by construction, and an entry with no path is refused there too. The directory WALK waits for the command, since a directory absent from this machine must not fail a deploy that never touches certificates. See operations.md |
kubernetes.securityContext |
– | runAsUser/fsGroup for the pod. Omitted entirely when unset. Every id here and in containerSecurity is a whole number from 0 to 2147483647 in plain decimal digits, quoted or not – anything else is refused at load, because the value is written into the CR unquoted, where 010 would be read as 8 and a newline as extra YAML. "0" means the operator’s default (1000001 for users, 1000002 for groups), or on OpenShift an id the SCC assigns; it never means root |
kubernetes.containerSecurity |
– | runAsUser/runAsGroup for the broker container, with the id rule above. readOnlyRootFilesystem was removed: a read-only root filesystem is not supported, so the key is refused by name (see the removed keys). The CR has no privileged or allowPrivilegeEscalation field, so a rendered CR shows neither; the bundled operator 1.4.2 pins privileged: false, allowPrivilegeEscalation: false, runAsNonRoot: true, capabilities: drop: [ALL] and seccompProfile: RuntimeDefault on the broker container itself – which makes the operator version part of the security posture, see container-security.md |
kubernetes.ports |
– (the operator’s default) | The broker Service’s ports, each name=containerPort[:servicePort][/proto] (tcp-smf=55555:55556, tls-smf=55443/TCP; TCP or UDP, default TCP). Unset, the CR carries no service.ports, so the installed operator’s own default list applies – for the bundled operator, the 17 ports solace-util examples shows commented out, SSH on 2222 among them. A list you set REPLACES that default whole, so keep tcp-ssh if you want CLI over SSH; an empty list is the same as unset. Upgrading this tool changes nothing on the broker or its Service for an env file without the key: earlier builds wrote that same list into the CR, so the first broker deploy after the upgrade reports the CR configured and the operator’s default refills identical ports |
scaling.* |
see Scaling | Broker sizing, applied on every platform – the CR’s spec.systemScaling on Kubernetes, container environment variables on docker and podman |
scaling.maxConnections |
100 (Kubernetes) / 1000 (container) |
The Solace scaling tier. Fixes how many cores the broker gets and defaults its memory on every platform – see Scaling tiers |
<docker\|podman>.container.mem |
the tier’s memory | Container memory limit, in docker’s and podman’s own b\|k\|m\|g suffix (not Kubernetes’ Mi/Gi). Podman’s unit carries it as PodmanArgs=--memory=, not quadlet’s Memory= key, which needs podman 5.5 |
<docker\|podman>.container.cpuset |
0-(cores-1) from the tier |
WHICH host cpus the MESSAGING nodes may use – cpuset: in the compose file, PodmanArgs=--cpuset-cpus= in the quadlet unit. A list or range of cpu ids (0-3, 0,2,4) naming EXACTLY as many cpus as the tier sizes the broker for; which ones is yours |
<docker\|podman>.container.monitorCpuset |
0 |
WHICH host cpu the HA monitor runs on. Exactly one id: the monitor gets a fixed 1 cpu and 2g whatever the tier, since it arbitrates quorum and carries no spool |
<docker\|podman>.container.ulimits.core |
-1 |
The core-dump size limit: -1 for unlimited – Solace’s recommendation, so a crash can be diagnosed – or a size in bytes as a plain whole number. A dump is a copy of the broker’s memory, credentials included, so 0 is the setting for a host where none may reach disk; whether one is written at all is still the host’s kernel.core_pattern. Rendered as core: in the compose file and as both Ulimit=core= and LimitCORE= in the quadlet unit. The one tunable limit: nofile and memlock stay fixed |
<docker\|podman>.network.mode |
bridge |
bridge or host. bridge publishes network.ports and nothing else. It puts the broker on the engine’s own private network: a bridge on docker and rootful podman, while the quadlet states no Network= line, so rootless podman uses its rootless default (pasta, or slirp4netns before podman 5.0). Earlier builds defaulted to host, so a file that relied on that must now say mode: host or list network.ports – see the upgrade note. host is an opt-in widening (container-security.md, rule 1): the container shares the host’s network namespace and every broker listener binds directly on the host’s interfaces – SEMP on 8080 (and 1943 with TLS), SMF on 55555, SSH on 2222 and the rest of the image’s defaults – so what can reach the broker is decided by the host firewall, not by this file. Either way the container listens only on unprivileged ports (8008/1443/1943 rather than 80/443/843), which is what lets it run as a non-root user |
<docker\|podman>.network.ports |
– (publishes nothing) | What bridge publishes, passed through exactly as written and never defaulted: with no list nothing is published on the host’s interfaces, so other machines cannot reach the broker – though containers on the same engine network, and on a rootful engine the host itself, still can at the container’s own address. This tool reaches its own broker through <runtime> exec, which needs no published port; only an HA primary’s calls to its backup do. Each entry is host:container, optionally bound to one address (127.0.0.1:8080:8080, [::1]:8080:8080) and suffixed /tcp or /udp; either side may be a lo-hi range, and a container range needs a host range of the same size. Checked in either mode, since each entry is written into the quadlet unit as-is. An HA member on bridge publishes its redundancy ports itself – 8300-8302, 8741 and 55555 – and, for broker perform redundancy-test, the backup’s SEMP port (8080, or 1943 with TLS) must be reachable from the primary (operations.md). Binding a port to 127.0.0.1 keeps it off the network |
The broker.* keys sit at the top level rather than under kubernetes.* because every
platform runs these same post-deployment steps identically. cliScriptsDir,
hostDiagnosticDir and every domainCerts path are host paths and resolve against
the env file’s directory
(above) –
cliScriptsDir is also the only folder the script commands read, so the file name you give
cli-script or shell-script is looked up there and nowhere else;
productKeys holds licence strings and is never treated as a path.
kubernetes.tlsServerSecret and tls.cert/tls.certKey answer different questions –
what the Secret is CALLED, and what it is built FROM. Which of them you set decides who
owns the Secret, and whether there is one at all:
tls.cert + tls.certKey |
kubernetes.tlsServerSecret |
What happens |
|---|---|---|
| set | set, or unset to derive <kubernetes.name>-tls |
This tool builds the kubernetes.io/tls Secret from those files under that name, broker generate prints it ahead of the CR, broker deploy applies it (offering to restart a running broker when the certificate changed), broker configure server-certs rotates it and hot-swaps the certificate over the CLI with no restart, and broker remove deletes it |
| unset | set | The Secret must already exist – created by hand, by cert-manager, or by anything else. The CR references it by name and nothing here reads, applies, rotates or deletes it. broker remove leaves it alone, and the namespace gate counts it as someone else’s |
| unset | unset | No TLS Secret and no tls block in the CR: the broker serves no TLS from a Secret |
Set the pair or neither: one without the other is refused, because the Secret carries both
keys and a Secret with only tls.crt in it is one the broker cannot start a listener over.
Supplying the files without naming the Secret is not refused: the name is derived, so the
certificate always has a Secret to land in and a tls block to reach the broker through.
broker validate states which of them it is, and whether the name was derived, so a
missing Secret is not first discovered by a pod that will not mount.
tls.certPassphrase is docker/podman only. The CRD’s spec.tls carries only
serverTlsConfigSecret, certFilename, certKeyFilename and enabled – there is no
passphrase field and no Secret key the operator reads one from. On Kubernetes, supply an
unencrypted key or decrypt it into the Secret yourself; broker validate warns when the
key is set.
The broker CR’s adminCredentialsSecret names the Secret holding the broker’s admin
password. semp.adminPass and kubernetes.adminSecret decide which one it is, and who owns
it – the three states the TLS and image-pull Secrets have:
semp.adminPass |
kubernetes.adminSecret |
What happens |
|---|---|---|
| set | set, or unset to derive <kubernetes.name>-admin |
This tool builds the Secret under that name, with the monitor password and pre-shared key when those are set; broker generate prints it, broker deploy applies it and broker remove deletes it |
| unset | set | The Secret must already exist with the key username_admin_password. The CR references it and nothing here builds, applies or deletes it |
| unset | unset | The CR names no admin Secret. The operator generates <kubernetes.name>-pubsubplus-admin-creds with a random password, owns it, and deletes it with the broker |
Without semp.adminPass, broker perform semp-login-check reads the password back from
whichever Secret the broker uses, which needs get secrets in the namespace.
Three combinations are refused at load:
semp.monitorPass or redundancy.psk without semp.adminPass. Both are entries of the
Secret this tool builds from the admin password, and without one it builds none. A
referenced Secret, or one the operator generates, is never written to. Drop the key and the
operator generates its own, or set semp.adminPass.kubernetes.adminSecret in the operator’s own names for this broker
(<kubernetes.name>-pubsubplus-...). The operator owns those Secrets and deletes them with
the broker. Copy one under a name of your own and name the copy.kubernetes.tlsServerSecret: dev-broker-admin beside a derived admin Secret. One Secret
cannot carry both sets of keys.The broker reads its admin password once, on a fresh data volume. Solace applies the
password key only when a broker boots with no database; changing the Secret later changes
nothing in the broker. That matters most in the last state. broker remove keeps the data
by default but takes the operator’s generated Secret with the broker, so a later deploy onto
that data would get a new random password the broker ignores – in HA the standby and monitor
pods would never become Ready, and nothing would hold the working password. So:
broker validate shows the state, and warns in the last one.broker remove in the last state says where the password is and how to copy it first –
or, when the running broker’s CR still names an admin Secret this env file dropped, that
the Secret stays in place and how to keep using it.broker deploy in the last state refuses when an earlier broker’s data PVCs are still in
the namespace, or when the running broker’s CR names an admin Secret that the new one would
drop. Set semp.adminPass to the broker’s password, or kubernetes.adminSecret to a Secret
holding it. The check needs list on pubsubpluseventbrokers and persistentvolumeclaims
in the namespace, and is probed first like every other permission. Claims under a custom
volume mount are not checked, since they exist before a first deploy.Upgrading moves an unset name. Earlier builds defaulted kubernetes.adminSecret to
solace-admin-secret. An env file that sets semp.adminPass and no name now derives
<kubernetes.name>-admin: the next deploy builds that Secret and the CR switches to it –
under updateStrategy: automatedRolling the operator rolls the pods, while manualPodRestart
waits for broker restart – and solace-admin-secret stays behind, where it keeps the
namespace from being offered for deletion. Set kubernetes.adminSecret: solace-admin-secret to keep the
old name, or delete the old Secret by hand after the redeploy.
Every key under scaling applies to every platform. Only the delivery differs: Kubernetes
writes them into the broker CR’s spec.systemScaling, while docker and podman pass them to
the container as environment variables under the broker’s own setting names. One env file
therefore sizes the same broker whichever platform runs it.
Every setting is settable under either spelling. The scaling key column below is the
schema’s own friendly name; the destination column is the broker setting this tool emits for
it, which is also a legal key in the env file – system_scaling_maxconnectioncount: 1000
works exactly like maxConnections: 1000. Both write the same value. Setting the same
setting under both spellings at once fails to load, naming both keys – a file that could
hold a contradiction is worse than one that refuses it.
scaling key |
Broker setting (container env var / CR field) |
|---|---|
maxConnections |
system_scaling_maxconnectioncount |
maxQueueMessages |
system_scaling_maxqueuemessagecount |
maxKafkaBridge |
system_scaling_maxkafkabridgecount |
maxKafkaConnections |
system_scaling_maxkafkabrokerconnectioncount |
maxBridges |
system_scaling_maxbridgecount |
maxSubscriptions |
system_scaling_maxsubscriptioncount |
maxGuaranteedMsgMB |
system_scaling_maxguaranteedmessagesize |
maxSpoolUsageMB |
messagespool_maxspoolusage (now the same name on every platform) |
Every destination name above is the name BOTH platforms use: the key inside the CR’s
spec.systemScaling, and the environment variable on docker and podman. That is a constraint
rather than a coincidence, and it has a limit worth knowing – a systemd Environment= name
may contain only letters, digits and underscores, so a broker setting spelled with a hyphen
cannot reach a container this way and could not be added to the table above as it stands.
A key that is neither a friendly name nor a destination name – a typo, or a real broker
setting this tool does not map (system_scaling_maxtransactedsessioncount, say) – fails to
load exactly as an unknown key does anywhere else in this schema. cpu and messagingNodeCpu
are refused by name for a different reason: scaling.cpu is fixed by the maxConnections
tier and derived, so there is no key for it under either spelling (see Scaling tiers, below).
<docker|podman>.container.cpuset is not an exception to that: it says WHICH cpus, not how
many, and it takes its own default from the same tier.
Defaults are identical across platforms except maxConnections (100 on Kubernetes, 1000 on
containers) and maxSpoolUsageMB (10000 on Kubernetes, 100000 on containers).
scaling.maxConnections is the Solace scaling tier, and it decides how much CPU the broker
gets on all three platforms. The amount is not configurable: sizing a broker by connection
count and then sizing its CPU independently is how a 200k-connection broker ends up on two
cores. The two platforms express it differently – Kubernetes gets a CPU limit in the CR,
docker and podman get a cpuset, the host cpus the container may run on – and which cpus
those are is yours to change. Memory is the tier’s default and stays yours to override;
storage is untouched by the tier.
scaling.maxConnections |
CPU cores (fixed) | Container cpuset default |
Memory default (Kubernetes / container) |
|---|---|---|---|
100 (Kubernetes default) |
2 | 0-1 |
3410Mi / 3410m |
1000 (container default) |
2 | 0-1 |
6898Mi / 6898m |
10000 |
4 | 0-3 |
12435Mi / 12435m |
100000 |
8 | 0-7 |
30925Mi / 30925m |
200000 |
12 | 0-11 |
52581Mi / 52581m |
The value must be exactly one of those five. A value between tiers is rejected rather
than rounded, because Solace publishes no sizing for it. Override memory with
kubernetes.msgNode.mem (a Kubernetes quantity, Mi/Gi) or <docker|podman>.container.mem
(docker’s and podman’s own b|k|m|g suffix – the engines reject Mi, so the two spellings
are not interchangeable and the loader says so). Override which cpus with
<docker|podman>.container.cpuset.
Docker and podman carry the tier’s caps in the generated compose file (cpuset:,
mem_limit:) and quadlet unit (PodmanArgs=--cpuset-cpus=, PodmanArgs=--memory=), including a
rootless quadlet, whose user slice gets the cpuset controller from the Delegate= line
in the drop-in its rlimits already need.
Either way an existing container deployment needs a full
redeploy – not just a restart – to pick up a changed cap.
The monitor host is sized separately, and not from the tier at all: it arbitrates
quorum, carries no message spool and routes no traffic, so it gets a fixed 1 cpu and
2g on docker and podman however large the tier is. Only WHICH cpu is yours to set
(<docker|podman>.container.monitorCpuset, default 0); the count is fixed. Kubernetes
is unaffected – the operator sizes the monitor pod there, and this tool sets only its
monitorNodeStorageSize.
The container nofile, memlock and shared-memory limits are not tunable: the broker
needs one specific value for each, so this tool emits them and checks the host can grant
them – see The limits the container actually gets.
core is the exception: <docker|podman>.container.ulimits.core defaults to unlimited and
can be lowered, down to 0 for no dumps at all.
A rootless podman host has further prerequisites, all checked and refused rather than fixed:
see Rootless podman prerequisites. The mode
itself is declared, then checked against who runs the command.
Every secret field takes either the value itself or – through a sibling *Env key –
the name of an environment variable to read it from. Setting both is an error, and so
is naming a variable that is unset or empty: the load fails naming the key and the
variable rather than deploying a broker with a blank password.
| Value key | Reference key |
|---|---|
semp.adminPass |
semp.adminPassEnv |
semp.monitorPass |
semp.monitorPassEnv |
semp.additionalUsers[].password |
semp.additionalUsers[].passwordEnv |
tls.certPassphrase |
tls.certPassphraseEnv |
image.pass |
image.passEnv |
redundancy.psk |
redundancy.pskEnv |
replication.sites[].via.semp.pass |
replication.sites[].via.semp.passEnv |
semp:
adminPassEnv: SOLACE_ADMIN_PASS # export SOLACE_ADMIN_PASS before any command
With the *Env form the env file carries no secret and is safe to commit and share. A
value is otherwise used verbatim on every platform – a $VAR or ${VAR} inside one
is a literal password, never expanded.
The pre-shared key is mandatory on docker and podman when redundancy.enabled is true
and optional on Kubernetes, and it is refused at load when it is missing where it is
required – the error carries the
openssl rand -base64 32 command. Nothing in this tool generates it. An earlier version
made one on a first HA deploy and rewrote the env file; that is gone, because it only ever
ran on one host, the value still had to be copied to the other two by hand, and a deploy
that edits the file it was handed is a surprise on a file that may be version-controlled. Nothing distributes a key across three container hosts, so each host’s
env file must carry the same value or the group cannot form. On Kubernetes the operator
generates and distributes one itself when the key is empty, and the CR’s
spec.preSharedAuthKeySecret is then omitted entirely; set it and the value is written as the
preshared_auth_key entry of the admin Secret this tool builds from semp.adminPass – the
same Secret the admin credentials live in – and the CR points at it. So on Kubernetes a key
needs semp.adminPass, and one without it is refused at load.
semp.monitorPass follows the same rule on Kubernetes, including needing
semp.adminPass. Set, it is written as the username_monitor_password entry of the admin
Secret this tool builds and the CR’s
spec.monitoringCredentialsSecret points there. Unset, the field is omitted and the operator
generates <kubernetes.name>-pubsubplus-monitor-creds with a random password of its own –
naming the admin Secret without that entry would point the broker’s monitor user at a file
that is not there. Nothing in this tool logs in as the monitor user, and it never enables the
operator’s Prometheus exporter, that user’s only consumer. Switching between the two on a live
broker changes the CR spec, which the operator applies like any other spec change – a rolling
restart under updateStrategy: automatedRolling. Upgrading this tool is such a switch for
an env file with no semp.monitorPass: earlier builds named kubernetes.adminSecret for the
monitor user regardless, so the first broker deploy after the upgrade drops the field and
rolls the pods, with no edit to the env file. Schedule that deploy, or set semp.monitorPass
first to keep the CR as it was – and, if the env file names no kubernetes.adminSecret, set
kubernetes.adminSecret: solace-admin-secret too, since that name moves as well (see
The admin Secret).
The tool never echoes a secret. Values piped to a command on stdin show as
<<< (N bytes on stdin) under -v/--verbose, values passed to a child process’s environment
as NAME=***, and validate/broker status report only whether each one is set. The
one exception is explicit and Kubernetes-only: there a Secret manifest IS the artifact, so
broker generate and operator generate print the values they would apply. On docker and
podman neither prints a secret at all – the artifact references them by name and only
broker deploy handles the values. See
Rendering without applying for where each secret
lands at rest.
replication: describes a DR pair: two separate brokers, each message-VPN active at one
site and standby at the other. It is not redundancy:, which is the three nodes of one HA
group – a replicated deployment usually has both, an HA group at each site. Omit the whole
section unless this broker replicates.
Two commands read it. broker configure data-replication converges THIS broker to it – the
mate’s addresses, which VPNs replicate, and each one’s role – and never contacts the mate.
broker perform data-replication verifies both brokers and moves roles across the pair. See
Data replication for what each does and when to run it.
The block is byte-identical at both sites. Nothing in it is written from one broker’s
point of view: there is no mate: key and no “my role”, both of which would have to be
reversed in the other site’s file. Each broker reads its own show router-name instead,
finds itself among sites[].routerNames, and whichever entry is not itself is its mate. So
the same text is pasted into both env files, and a failover is one edit – change one
activeAt – rather than two files kept in step.
replication:
sites: # exactly 2
- virtualRouterName: "v:sg1" # quote it: the colon is a YAML indicator
routerNames: [sg1, sg1b] # every node of this site's HA group
endpoints:
- { host: 10.160.132.1, port: 55443, transport: ssl }
via: # optional; read only from the OTHER site
kubernetes: { command: kubectl --context sg, namespace: solace-sg, name: solace }
- virtualRouterName: "v:dr1"
routerNames: [dr1]
endpoints:
- { host: 10.150.132.1, port: 55443, transport: ssl }
via:
semp: { host: 10.150.132.1, port: 1943, tls: true, passEnv: SOLACE_DR_ADMIN_PASS }
vpns:
- { name: ORDERS, activeAt: "v:sg1" }
- { name: PAYMENTS, activeAt: "v:dr1" }
| Key | Purpose |
|---|---|
replication.sites |
Exactly 2 entries. Replication is a pair; one site or three is refused at load |
sites[].virtualRouterName |
The site’s key: what vpns[].activeAt references, and the literal operand the mate is given as replication mate virtual-router-name. Mandatory and never derived – the file states the exact string the broker CLI will be handed, so nothing in the mate-address path is inferred. The two sites’ values must differ. Quote it: a bare v:sg1 does parse – a colon ends a plain scalar only when a blank follows it – but quoting a value whose whole point is a literal colon leaves nothing to reason about, and it is what the annotated sample teaches |
sites[].routerNames |
What this site’s brokers answer to, matched against their own show router-name so a broker can find itself in this file. A list, because a site is usually an HA group and the backup node reports its own name – list every node. Non-empty, and no name may appear under both sites |
sites[].endpoints |
How the other broker dials this one. At least one, at most 2 per transport. This tool never dials them: they are rendered into the mate’s own CLI lines |
endpoints[].host / .port |
The address, port 1-65535. Against an appliance mate every endpoint must carry the same host and a distinct transport: that grammar has one connect-via address and one non-repeatable connect-port per transport, so a second host or a second port of the same transport is refused when the lines are rendered rather than silently dropped |
endpoints[].transport |
plainText (what an omitted transport means), compressed or ssl. encrypted is the routing name for the same thing and is refused by name, pointing at ssl |
sites[].via |
How this tool reaches that site when it is the mate. Exactly one child, kubernetes: or semp: – the key present IS the mechanism, so a via cannot name one thing and configure another. Read only from the OTHER site’s entry: a broker takes its own access from this file’s kubernetes:/docker:/podman: section like every other command |
via.kubernetes.command |
The cluster CLI this tool runs to reach that site. Carry the cluster in it (kubectl --context dr) rather than beside it. Restricted – see The command fields are executable content |
via.kubernetes.namespace / .name |
The mate’s namespace, and its PubSubPlusEventBroker name, which its pod is named after |
via.semp.host / .port |
The mate’s SEMP address. Not derived from endpoints: replication runs over the message backbone, so a reachable replication endpoint proves nothing about SEMP reachability |
via.semp.tls / .insecure |
https rather than http, and whether to skip certificate verification for a self-signed mate. Declared, never inferred from this broker’s own posture – this hop is a WAN rather than a rack |
via.semp.pass / .passEnv / .passSecret |
Exactly one of the three supplies the mate’s admin password, never this deployment’s semp.adminPass – a DR site is a different broker, and reusing this one’s password would fail at best and hide the mistake if the two happened to match. passSecret is {namespace, name, key} of a Kubernetes Secret, read with that site’s own via.kubernetes.command when it has one and this file’s kubernetes.command otherwise. The username is always admin, and there is no prompt for the password |
replication.vpns[].name |
Listing a VPN enables replication for it at both sites. A VPN replicating on the broker but absent from this list has its replication shut down by broker configure data-replication – the file is authoritative. Each name appears once |
replication.vpns[].activeAt |
Which site holds the active role for that VPN; the other is standby. It names a virtualRouterName, not a router name |
via is optional, and a file whose sites declare none is valid. Only
broker perform data-replication needs it, and it checks both sites’ blocks in its own
preflight before anything is written; requiring it at load would refuse a file that
configures replication perfectly well with the local-only command. A via: key with nothing
under it decodes to the same value as an absent one, so it is accepted here too and the
switch command is what reports a site it cannot reach.
Everything else is checked at load: two sites, a virtualRouterName on each and no
duplicate, non-empty non-overlapping routerNames, at least one endpoint with a valid port
and transport and no more than two per transport, VPN names that appear once, and an
activeAt naming a declared site. A partially written block is an error rather than a
half-configured switchover waiting to happen.
kubernetes.command, docker.command, podman.command, docker.compose and each
replication.sites[].via.kubernetes.command name a binary this tool runs on your
machine. Env files travel – repositories, pull requests, shared
archives – so the person who wrote one is routinely not the person who runs it. Treat an
env file the way you would treat a script someone sent you: read the command fields before
running anything with it.
To make that review short, the fields are restricted. A command is accepted only when:
$ ; | & < > ( ) * ? [ ] { } # !. Nothing is ever
passed through a shell, so these are not injections – but tokens end up in logs and in
the -v/--verbose exec trace, and a token you cannot see is one you cannot
review. A Windows path in a flag value therefore needs forward slashes:
--kubeconfig C:/Users/you/.kube/config.~ is expanded, not refused – in every token but the first, to the home
directory of whoever runs the tool, exactly as in a path key.
So command: oc --kubeconfig ~/solace/kubecontext works. A tilde anywhere else in a token
is an ordinary character (an 8.3 short name such as C:/Users/RUNNER~1/.kube/config is
what expansion itself produces on Windows), and a ~ that is still leading when the guard
runs is refused – nothing downstream would expand it.The binary is a bare name from the allowlist. No / or \ anywhere in it, and no
leading ~ either – it is the one token that is not expanded, because a path
would run a file the env file chose – such as a ./kubectl unpacked beside it – rather
than the one on your PATH. One optional .exe is stripped, then the name must be:
| Platform | Allowed |
|---|---|
| Kubernetes | kubectl, oc |
| Docker | docker, docker-compose, nerdctl |
| Podman | podman |
A replication site’s via.kubernetes.command is always held to the Kubernetes row
whatever platform this end runs on: the mate may sit in a cluster while the local broker
runs on docker, and the binary being run is a cluster CLI either way.
Nothing after it is a bare word. Flags and their values are fine
(kubectl --context prod -n solace); a bare word is not, because this tool appends its
own subcommand and a word in that position would run ahead of it. kubectl delete in a
config is exactly the attack. The literal -- is refused for the same reason.
One acknowledged gap, in that last rule. The check cannot know how many values a flag takes – that would mean carrying a table of every flag of every allowed CLI, which would rot as those CLIs change – so the token after any flag is accepted as that flag’s value. After a flag that takes no value, that token is not a value at all, and it lands in subcommand position after all:
kubernetes.command: kubectl --insecure-skip-tls-verify delete # accepted; runs `delete`
docker.compose: docker-compose --verbose down # the container equivalent
This is a known limit, not an oversight, and it is why the guarantee is stated narrowly:
argv[0] and every bare token are checked; the contents of a flag value are not. It
costs an attacker nothing more than the access they already need – anyone who can edit
the env file can also point a perfectly legitimate kubectl at your production cluster,
which no check in this file can detect. Both are review problems: read an env file’s
command fields the way you would read a script it ships.
Anything else – a wrapper such as microk8s kubectl or lima nerdctl, a site-specific
shim – runs only when you approve it, per invocation:
solace-util broker deploy --allow-command microk8s # kubernetes env file wrapping kubectl in microk8s
solace-util broker deploy --allow-command lima # docker/podman env file wrapping the runtime in lima
--allow-command is repeatable, takes a bare name (never a path), and exists only as a
command-line flag. There is deliberately no env-file key, environment variable, or any other
way for a config to widen its own allowlist: the authority to run something unusual belongs
to the person who can see what they are approving. It is rejected on any generate command,
where nothing executes.
Privilege escalation is never approvable, by the config or by you: sudo, sudoedit,
doas, su, pkexec, run0, systemd-run, machinectl, setpriv, capsh, unshare,
nsenter, runas and gsudo are refused as --allow-command values, in any casing
(Sudo and SUDO.exe too – Windows and a default macOS filesystem resolve those to the same
binary, so matching them exactly would have let a capital letter through a floor that is
supposed to stop everyone). The allowlist in rule 2 above is the opposite: it stays
case-sensitive on purpose, because KUBECTL is a genuinely different file where filesystems
say it is, and a positive match that folded case would approve a binary nobody listed. This is
not a
ban on running as root – rootful podman needs it. It is about where you elevate. A
command: sudo podman elevates every command this tool issues, for the whole life of an env
file, decided by whoever wrote that file. Elevate the tool instead, at the moment you run it,
so the privilege belongs to one invocation you chose:
sudo solace-util broker deploy -e prod.yaml # yes (prod.yaml is a podman env file)
# command: sudo podman in the env file # never
The same check runs twice – once when the env file is loaded, and again immediately before any command line is built – from a single implementation, so a hostile file is inert even on a path that skipped validation.
What this does not protect against. Two things are out of scope, and no amount of parsing would fix either:
kubectl on your
PATH, they own the host; nothing this tool checks can help. What it does do is make the
binary’s real location visible – before any work starts, each binary this env file names
(kubernetes.command, docker.command, podman.command, docker.compose) is resolved and
printed as ==> using <name>: <resolved path>, and -v/--verbose prints every command as
it runs – and refuse to resolve a bare name from the current directory.kubernetes.namespace: production,
or a valid kubectl --context aimed at the wrong cluster, is a review problem. So is a
flag’s value: this tool cannot know how many arguments a flag takes, so the token after
--kubeconfig is accepted as that flag’s value whatever it says. The hard guarantee covers
the binary and every bare word – not flag values.Rendering executes nothing: every generate command only ever calls the templating package,
never an external command, so pointing one at an env file you did not write cannot run
anything. Note that it still loads the file, so one whose command field breaks the rules
above fails there rather than printing an artifact – which is itself the answer you wanted
about that file. To read a command field without loading anything at all, open the file.
An env file written for an earlier build still decodes these keys, and the load then fails naming the replacement. That is deliberate: a bare “unknown field” error would tell you the key is wrong without telling you what to write instead.
| Old key | Now |
|---|---|
kubernetes.runtime |
kubernetes.command |
docker.runtime |
docker.command |
podman.runtime |
podman.command |
broker.cliScriptsFolder |
broker.cliScriptsDir |
broker.diagDir |
broker.hostDiagnosticDir |
broker.domainCerts.folder |
broker.domainCerts.dirs (see below) |
The three command renames make the platform blocks agree with
replication.sites[].via.kubernetes.command, which always spelled it that way.
broker.domainCerts.folder changed shape as well as name, so the error says so. dirs
is a list of directories to walk, each entry either a plain path or a mapping with path
and an optional comma-separated fileExt (default .cer, .crt, .pem); files now
takes a full host path as its value rather than a bare filename that used to be joined
onto folder. A certificate loaded from dirs is
named <last directory element>_<filename>, which is what files exists to override when
you want a name of your own or one file out of a directory of many.
Some keys were removed rather than renamed. Each still decodes and then fails to load naming why, for the same reason the renames do:
| Removed key | Why, and what to do instead |
|---|---|
kubernetes.msgNode.cpu |
Broker CPU is fixed by the scaling tier – see Scaling tiers |
scaling.maxPool |
Named the same broker setting as scaling.maxSpoolUsageMB; use that |
<docker\|podman>.container.shmSize |
/dev/shm is fixed at 2g, which is what the broker needs |
<docker\|podman>.container.ulimits.nofile, .ulimits.memlock |
Fixed at 2448:1048576 and -1. The host is checked against them – see The limits the container actually gets. ulimits.core was NOT removed: it is live, see The keys |
kubernetes.containerSecurity.readOnlyRootFilesystem |
A read-only root filesystem is not supported on any platform, so the key is refused whether it says true or false; the operator’s default, a writable root, applies. runAsUser and runAsGroup are unaffected |
solace-util convert)The pre-Go scripts kept their configuration in shell files under bash/env/, sourced by
000-env.sh. solace-util convert turns one into the YAML this CLI reads:
solace-util convert bash/env/prod -o prod.yaml # kubernetes flavour
solace-util convert bash/docker-podman/env/prod -o prod.yaml # docker/podman flavour
solace-util validate -e prod.yaml
SOLBK_NS/SOLOP_* ->
kubernetes, SOLBK_NODE_*/DOCKER_MODE/PODMAN_ROOTLESS -> docker/podman). Pass
--platform kubernetes|docker|podman to choose it yourself; the choice is echoed either way.( ... ) arrays, declare -A maps, export/declare prefixes, ${VAR}
references, and trailing comments are all understood). Shell constructs beyond
assignments are skipped.SOLBK_REDUNDANCY.REPL_MATE, REPL_CONN_SSL and REPL_PSK are read and deliberately not carried
over, with a warning naming all three. They describe one mate; the replication: block
describes both sites of the pair and needs values a bash env file does not hold, so a
converted block could not validate. Write it by hand – see Replication.-o the YAML goes to stdout (warnings stay on stderr). With -o the file is
written 0600, and an existing file is confirmed before it is replaced – --no-prompt
answers yes, and a run with no terminal keeps the file and says so.The output carries every secret from the source file verbatim – treat it like the source,
and never commit it. (Switch the values to their *Env reference keys afterwards and it
becomes safe to commit; see Secrets.) SOLBK_USR_SECRET converts to
kubernetes.adminSecret (SOLBK_ADM_SECRET is accepted as an alias for the same key;
when both are set and disagree, the canonical SOLBK_USR_SECRET wins with a warning; when
neither is set, solace-admin-secret is written out – the bash tool’s default, which the
legacy broker was built under, where this schema would derive <kubernetes.name>-admin),
SOLBK_SVR_SECRET to kubernetes.tlsServerSecret, IMAGEREPO_SECRET to
kubernetes.imagePullSecret (on a docker/podman conversion those two are dropped with a
warning naming that kubernetes-only home – they name Kubernetes Secret objects, which have
no container equivalent), and each SOLBK_USR_PASS entry to an semp.additionalUsers entry with
accessLevel: none – the bash flow set no level, so the converter picks the least
privileged one and says so; raise it per user as needed.
kubernetes.msgNode.cpu and scaling.maxPool fail to load, each naming its
replacement. Broker CPU is fixed by the scaling tier rather than set by hand, and
maxPool would name the same broker setting as scaling.maxSpoolUsageMB – one concept
under two platform-specific keys, which the scaling block keeps under a single name today.
That is not the same thing as the dual-spelling alias every other scaling setting now
gets (see Scaling): maxPool and maxSpoolUsageMB had no defined winner if a
file set both, where an alias pair does – setting both spellings of one setting fails to
load, naming both. kubernetes.msgNode.mem is unaffected. Docker always deploys through
compose, so there is no docker.mode key to choose a mode with; a file still carrying it
fails strict decoding as an unknown field. See Scaling.