Skip to content

Load mirth.properties.d drop-in configuration overrides on startup - #405

Open
pacmano1 wants to merge 3 commits into
OpenIntegrationEngine:mainfrom
pacmano1:feat/mirth-properties-drop-in-dir
Open

Load mirth.properties.d drop-in configuration overrides on startup#405
pacmano1 wants to merge 3 commits into
OpenIntegrationEngine:mainfrom
pacmano1:feat/mirth-properties-drop-in-dir

Conversation

@pacmano1

@pacmano1 pacmano1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #50.

Files named *.properties under conf/mirth.properties.d are loaded on top of mirth.properties at startup, in lexical filename order, later files winning. The server never writes to the directory, so local configuration survives upgrades without hand-merging. This mirrors the existing vmoptions convention (base_includes.vmoptions -> custom.vmoptions).

All six mirth.properties readers apply the overlay: the launcher, the main server, the configuration controller, the WebStart servlet, extension statuses, and the Rhino optimization-level lookup in JavaScriptScopeUtil. The configuration controller keeps a separate file-backed config for version migration and password re-encryption, so those writes never bake drop-in values into mirth.properties.

Scope notes:

To verify: create conf/mirth.properties.d/10-test.properties containing http.port = 8199, start the server via oieserver, confirm the startup log reports the web server on port 8199 and that conf/mirth.properties is unchanged afterward.

Files named *.properties under conf/mirth.properties.d are applied on top
of mirth.properties in lexical filename order, later files winning. The
server never writes to the directory: migration and password re-encryption
operate on a separate file-backed configuration, so drop-in values are
never baked into mirth.properties.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Test Results

683 tests   683 ✅  2m 12s ⏱️
115 suites    0 💤
115 files      0 ❌

Results for commit 0a23598.

♻️ This comment has been updated with latest results.

Replaces the README: the sample carries the documentation as comments and
is ignored by the loader until copied to a *.properties name. Also corrects
the mirth.properties header comment - the server does not rewrite this file
in normal operation; the drop-in directory exists for upgrade hygiene.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
@pacmano1

Copy link
Copy Markdown
Contributor Author

Design notes, so the reasoning doesn't live only in my head:

  • A drop-in directory over a special sibling file (custom.properties, local.properties): matches the conf.d/sysctl.d convention, scales past one file, and the name derives from the parent file. The vmoptions chain (base_includes -> custom.vmoptions) is the in-repo precedent for layered config.
  • The sample ships as mirth.properties.example so the loader ignores it; copy it to a *.properties name to activate.
  • The server does not modify mirth.properties in normal operation (verified across a boot: identical content). First boot only trims trailing whitespace on empty values, appends ;upgrade=true to the Derby URL, and generates keystore passwords. So the directory exists for upgrade hygiene: the installer preserves mirth.properties, and tar.gz upgrades can replace it wholesale without hand-merging.
  • Keystore gotcha, verified live on a fresh install: keystore.storepass/keystore.keypass auto-generate only when BOTH are still the shipped default. A drop-in overriding just one suppresses generation and the other stays at its published default. Pre-existing guard; the sample file documents it.
  • Drop-ins are parsed with java.util.Properties in every reader so the launcher-side and server-side worlds see identical semantics. ${...} references would interpolate only on the server side, so the sample avoids suggesting them.
  • log4j2 layering split to Investigate layered/drop-in configuration for log4j2.properties #404. Renaming the shipped defaults file (also floated in [IDEA] Allow multiple MC configuration files t be read on startup #50) belongs to the branding effort (Alter the build pipeline so that build artifacts are branded as "openintegrationengine" or "oie" instead of "mirth" #165/Standardize artifact naming to align with project branding #220).

@mgaffigan mgaffigan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we collapse this to a global store? Each consumer should not have to construct this themselves, nor should we be reading it so many times during boot and operation without reason.

Also also: if we're doing this, can we sneak in or at least leave room for configuration via environment variables taking precedence over everything?

@pacmano1

Copy link
Copy Markdown
Contributor Author

Both good.

Store: agreed. DefaultConfigurationController already loads mirth.properties once, so I'll point Mirth, WebStartServlet, and JavaScriptScopeUtil at that copy instead of each reopening the file (WebStartServlet re-reads on every JNLP request). MirthLauncher and ExtensionStatuses run before the server classpath exists, so those two keep their own read. I'll do that here.

Env: reading this as the Docker path. configure-from-env writes env into mirth.properties, so with these drop-ins on top a drop-in now overrides an env value, backwards from "env wins." Plan: have configure-from-env write to a last-sorted drop-in (99-env.properties) instead, so env wins. Docker follow-up, not this PR, so this one just leaves room. Shout if you meant something broader.

Add ConfigurationController.getPropertiesConfiguration(), which returns
mirth.properties with the conf/mirth.properties.d drop-ins already
applied. Mirth and WebStartServlet now read that single shared copy
instead of each loading and merging the file themselves. The returned
configuration is owned by the controller and must be treated as
read-only; the controller writes to it during startup (for example when
it generates keystore passwords), and consumers only read it.

A drop-in file that cannot be read or parsed now fails closed. Previously
overlay() threw during controller initialization, where the exception was
swallowed and the server came up on the base configuration with the
drop-ins silently dropped; if a drop-in was hardening a setting, that
reverts to the weaker base value. Catch the failure, record it, and
refuse to start with a clear message. The launcher's early readers stay
lenient and skip a bad file with a warning, matching how they already
treat mirth.properties. A malformed \uXXXX escape is treated the same as
an unreadable file.

Add a regression test that a write to the merged configuration does not
leak back into the base file saved to mirth.properties.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
@pacmano1

Copy link
Copy Markdown
Contributor Author

Consolidated config loading. Mirth and WebStartServlet no longer each load and merge mirth.properties + the drop-ins. They read one shared, already-merged copy from ConfigurationController.getPropertiesConfiguration(). saveMirthConfig() still writes only the base file, so drop-in values never get baked into mirth.properties when the server re-encrypts a password or generates keystore passwords. JavaScriptScopeUtil stays self-contained on purpose: its static initializer runs in unit tests with no server, so it can't call the controller.

A bad drop-in file now fails closed. Two cases:

  • Bad file (unreadable, or a malformed \uXXXX escape): before, overlay() threw during init, the throw was swallowed, and the server came up on the base config with the drop-ins dropped. That's fail-open: if a drop-in was hardening a setting (restricting TLS, forcing HTTPS), the server reverts to the weaker base value with no signal. Now the controller catches it and the server refuses to start with a clear message. The bootstrap readers (MirthLauncher, ExtensionStatuses) stay lenient and skip a bad file with a warning, matching how they already treat mirth.properties; they only read low-stakes values, and the authoritative config is gated on the server path.
  • Bad value in a good file (http.port = banana): not new, and not something the loader can judge. It behaves the same as that value in mirth.properties today, so it's left as-is. Real config validation is a separate change.

Also documented that getPropertiesConfiguration() hands out the controller's live config and consumers must treat it as read-only. Enforcing that with an immutable view changes the type through the web server and TLS path, so it's documented here for now and I'll file a follow-up to enforce it.

Verified: full clean build, 683 tests. Booted via oieserver: a valid drop-in (http.port override) boots clean and binds the overridden port. A malformed drop-in and an unreadable (chmod 000) drop-in each make the engine log Refusing to start and bind no ports.

@pacmano1

Copy link
Copy Markdown
Contributor Author

Heads up on CI: the build check failed once on this commit, but it was a flaky test (FileReceiverTest.testPoll1, a file-count assertion unrelated to this change). It passed on re-run and doesn't reproduce locally on macOS or on a native-Linux run of the same commit, so all checks are green now. Root cause of the flake (server tests share one JVM and FileReceiverTest injects a mock ControllerFactory into global static state) filed as #414.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[IDEA] Allow multiple MC configuration files t be read on startup

2 participants