Versioning: INBUXA's own dated version, with the Stalwart base shown

inbuxa --version, the banner, startup events, OpenTelemetry and the JMAP
implementation string read "2026.9.18 (Stalwart 0.16.22)". The base comes
from Cargo, which keeps following upstream so version bumps merge cleanly.
Received headers and IMAP ID carry the INBUXA version.
This commit is contained in:
2026-09-18 12:08:41 -07:00
parent bde11d54c3
commit faedf7a1da
10 changed files with 70 additions and 16 deletions
+2 -2
View File
@@ -543,10 +543,10 @@ impl Metrics {
let metrics = bp.setting_infallible::<structs::Metrics>().await;
let resource = Resource::builder()
.with_service_name("stalwart")
.with_attribute(KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")))
.with_attribute(KeyValue::new(SERVICE_VERSION, types::brand_version_full!()))
.build();
let instrumentation = InstrumentationScope::builder("stalwart")
.with_version(env!("CARGO_PKG_VERSION"))
.with_version(types::brand_version_full!())
.build();
Metrics {
+1 -1
View File
@@ -79,7 +79,7 @@ pub static VERSION_PRIVATE: &str = env!("CARGO_PKG_VERSION");
pub static VERSION_PUBLIC: &str = "1.0.0";
pub static USER_AGENT: &str = concat!(types::brand!(), "/1.0.0");
pub static DAEMON_NAME: &str = concat!(types::brand!(), " v", env!("CARGO_PKG_VERSION"),);
pub static DAEMON_NAME: &str = concat!(types::brand!(), " v", types::brand_version!(),);
pub static PROD_ID: &str = types::brand_prodid!();
/*
+6 -6
View File
@@ -39,8 +39,8 @@ pub struct IpcReceivers {
const HELP: &str = concat!(
types::brand_server!(),
" v",
env!("CARGO_PKG_VERSION"),
" ",
types::brand_version_full!(),
r#"
Usage: inbuxa [OPTIONS]
@@ -88,7 +88,7 @@ impl BootManager {
std::process::exit(0);
}
("version" | "V", _) => {
println!("{}", env!("CARGO_PKG_VERSION"));
println!("{}", types::brand_version_full!());
std::process::exit(0);
}
("config" | "c", Some(value)) => {
@@ -166,20 +166,20 @@ impl BootManager {
Hostname = bootstrap.registry.local_hostname().to_string(),
Details =
"No configuration file was found. Port 8080 is open for initial setup.",
Version = env!("CARGO_PKG_VERSION"),
Version = types::brand_version_full!(),
);
} else if bootstrap.registry.is_recovery_mode() {
trc::event!(
Server(trc::ServerEvent::RecoveryMode),
Details = "Port 8080 is open for troubleshooting and recovery.",
Hostname = bootstrap.registry.local_hostname().to_string(),
Version = env!("CARGO_PKG_VERSION"),
Version = types::brand_version_full!(),
);
} else {
trc::event!(
Server(trc::ServerEvent::Startup),
Hostname = bootstrap.registry.local_hostname().to_string(),
Version = env!("CARGO_PKG_VERSION"),
Version = types::brand_version_full!(),
);
}
+2 -2
View File
@@ -13,8 +13,8 @@ use store::{Deserialize, IterateParams, SUBSPACE_INDEXES, SUBSPACE_REGISTRY_IDX,
const HELP: &str = concat!(
types::brand_server!(),
" v",
env!("CARGO_PKG_VERSION"),
" ",
types::brand_version_full!(),
r#" Data Store CLI
Enter commands (type 'help' for available commands).
+2 -2
View File
@@ -28,11 +28,11 @@ pub(crate) fn spawn_otel_tracer(builder: SubscriberBuilder, mut otel: OtelTracer
tokio::spawn(async move {
let resource = Resource::builder()
.with_service_name("stalwart")
.with_attribute(KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")))
.with_attribute(KeyValue::new(SERVICE_VERSION, types::brand_version_full!()))
.build();
let instrumentation = InstrumentationScope::builder("stalwart")
.with_version(env!("CARGO_PKG_VERSION"))
.with_version(types::brand_version_full!())
.build();
otel.log_exporter.set_resource(&resource);
+3 -1
View File
@@ -70,7 +70,9 @@ impl<T: SessionStream> Session<T> {
concat!(
"* ID (\"name\" \"",
types::brand!(),
"\" \"version\" \"1.0.0\" \"vendor\" \"",
"\" \"version\" \"",
types::brand_version!(),
"\" \"vendor\" \"",
types::brand!(),
"\" \"support-url\" \"",
types::brand_url!(),
+1 -1
View File
@@ -407,7 +407,7 @@ impl Session {
impl Default for SieveSessionCapabilities {
fn default() -> Self {
Self {
implementation: concat!(types::brand!(), " v1.0.0"),
implementation: concat!(types::brand!(), " ", types::brand_version_full!()),
}
}
}
+22
View File
@@ -64,3 +64,25 @@ pub fn env_var(name: &str) -> Result<String, std::env::VarError> {
found => found,
}
}
/// INBUXA's own version, dated like the rest of its family: `YYYY.M.D`, with
/// a letter or `.N` suffix for a second release on one day. It's set here and
/// not in Cargo.toml, so upstream's version bumps merge without conflicts.
#[macro_export]
macro_rules! brand_version {
() => {
"2026.9.18"
};
}
/// The version with the Stalwart release it's built on, e.g.
/// `2026.9.18 (Stalwart 0.16.22)`. The base comes from Cargo, which follows
/// upstream, so it's always the base actually compiled in. It matters because
/// Stalwart's data upgrades are one-way. Once INBUXA stops tracking upstream,
/// this becomes just the version.
#[macro_export]
macro_rules! brand_version_full {
() => {
concat!($crate::brand_version!(), " (Stalwart ", env!("CARGO_PKG_VERSION"), ")")
};
}
+30
View File
@@ -209,6 +209,36 @@ Done 2026-09-18:
- `install.sh` is a stub that says there's no release yet. Upstream's version
would download and install Stalwart itself.
### 2.6 Versioning
Decided 2026-09-18.
- **INBUXA has its own version,** dated like the rest of its family
(ihasmail, ihasmail-oneshot, stalwart-migrator): `YYYY.M.D`, tagged
`vYYYY.M.D`, with a suffix for a second release on one day. The server's is
`types::brand_version!()`, and INBUXA Admin has its own.
- **The Stalwart base stays visible** while INBUXA tracks upstream:
`inbuxa --version`, the startup banner and events, OpenTelemetry's
`service.version`, and the JMAP `implementation` string all read
`2026.9.18 (Stalwart 0.16.22)`. It matters because Stalwart's data upgrades
are one-way, so anyone upgrading needs to know which base their data will be
converted to. Release notes and the strip report say it too.
- **Cargo versions follow upstream, untouched.** Every upstream release bumps
all ~30 manifests, so overriding them would conflict on every sync. The base
shown is read from Cargo, so it's always what was actually compiled in.
Upstream's internal data-format version (`types/src/semver.rs`), which drives
store migrations, is upstream's and is never changed.
- `DAEMON_NAME` (the `Received:` header) and the IMAP `ID` response carry the
INBUXA version alone. `VERSION_PUBLIC`'s deliberately vague `1.0.0` for Sieve
is left as upstream has it.
- **Stalwart 1.0** arrives like any release (strip, merge, release under the
next date), but as its own milestone: a merge dry run, a strip-report
comparison against 0.16, and an upgrade test on a copy of real data, because
it will almost certainly bring a one-way store migration.
- **Diverging from upstream** is expected eventually, though not soon. When it
happens, the base drops out of the version string and nothing else about
versioning changes.
## 3. Clean room
INBUXA runs on a paid Stalwart Enterprise license, so its maintainer is a
+1 -1
View File
@@ -54,7 +54,7 @@ pub async fn test(test: &TestServer) {
"urn:ietf:params:jmap:submission": {},
"urn:ietf:params:jmap:vacationresponse": {},
"urn:ietf:params:jmap:sieve": {
"implementation": "INBUXA v1.0.0"
"implementation": concat!(types::brand!(), " ", types::brand_version_full!())
},
"urn:ietf:params:jmap:blob": {},
"urn:ietf:params:jmap:quota": {},