Packaging: the binary and package are inbuxa, INBUXA_* settings with STALWART_* fallback

- crates/main: package and [[bin]] renamed to inbuxa; homepage inbuxa.org;
  license AGPL-3.0-only (upstream is dual; the fork takes the AGPL).
- types::branding::env_var reads INBUXA_<name>, falling back to
  STALWART_<name> with a warning, for all nine server settings.
  STALWART_APP_ and STALWART_SPAM_* storage keys are unchanged.
- New-install default paths /var/lib/inbuxa and /var/log/inbuxa.
- Dockerfiles, systemd unit, launchd plist and AppArmor profile renamed.
- Upstream's .github moved to .github-upstream so none of it runs.
- install.sh stubbed: upstream's would install Stalwart.
- Two missed brand strings: the SMTP Received header and the utils user agent.
This commit is contained in:
2026-09-18 11:09:22 -07:00
parent 5ce033e10c
commit d3f0b36dd2
35 changed files with 199 additions and 1205 deletions
Generated
+32 -32
View File
@@ -3923,6 +3923,38 @@ dependencies = [
"utils", "utils",
] ]
[[package]]
name = "inbuxa"
version = "0.16.22"
dependencies = [
"common",
"coordinator",
"dav",
"directory",
"email",
"groupware",
"http 0.16.22",
"http_proto",
"imap",
"jmap",
"managesieve",
"migration",
"pop3",
"registry",
"rustls",
"scim",
"services",
"smtp",
"smtp-proto",
"spam-filter",
"store",
"tikv-jemallocator",
"tokio",
"trc",
"types",
"utils",
]
[[package]] [[package]]
name = "include-flate" name = "include-flate"
version = "0.3.4" version = "0.3.4"
@@ -8222,38 +8254,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "stalwart"
version = "0.16.22"
dependencies = [
"common",
"coordinator",
"dav",
"directory",
"email",
"groupware",
"http 0.16.22",
"http_proto",
"imap",
"jmap",
"managesieve",
"migration",
"pop3",
"registry",
"rustls",
"scim",
"services",
"smtp",
"smtp-proto",
"spam-filter",
"store",
"tikv-jemallocator",
"tokio",
"trc",
"types",
"utils",
]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
+14 -14
View File
@@ -21,7 +21,7 @@ RUN rustup target add "$(cat /target.txt)"
COPY --from=planner /recipe.json /recipe.json COPY --from=planner /recipe.json /recipe.json
RUN RUSTFLAGS="$(cat /flags.txt)" cargo chef cook --target "$(cat /target.txt)" --release --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" --recipe-path /recipe.json RUN RUSTFLAGS="$(cat /flags.txt)" cargo chef cook --target "$(cat /target.txt)" --release --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" --recipe-path /recipe.json
COPY . . COPY . .
RUN RUSTFLAGS="$(cat /flags.txt)" cargo build --target "$(cat /target.txt)" --release -p stalwart --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" RUN RUSTFLAGS="$(cat /flags.txt)" cargo build --target "$(cat /target.txt)" --release -p inbuxa --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats"
RUN mv "/build/target/$(cat /target.txt)/release" "/output" RUN mv "/build/target/$(cat /target.txt)/release" "/output"
FROM docker.io/debian:trixie-slim FROM docker.io/debian:trixie-slim
@@ -29,18 +29,18 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \ apt-get update && \
apt-get install -yq --no-install-recommends ca-certificates curl libcap2-bin && \ apt-get install -yq --no-install-recommends ca-certificates curl libcap2-bin && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
groupadd -r -g 2000 stalwart && \ groupadd -r -g 2000 inbuxa && \
useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M stalwart && \ useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M inbuxa && \
mkdir -p /etc/stalwart /var/lib/stalwart && \ mkdir -p /etc/inbuxa /var/lib/inbuxa && \
chown stalwart:stalwart /etc/stalwart /var/lib/stalwart chown inbuxa:inbuxa /etc/inbuxa /var/lib/inbuxa
COPY --from=builder --chmod=0755 /output/stalwart /usr/local/bin/stalwart COPY --from=builder --chmod=0755 /output/inbuxa /usr/local/bin/inbuxa
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/stalwart RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/inbuxa
USER stalwart USER inbuxa
WORKDIR /var/lib/stalwart WORKDIR /var/lib/inbuxa
VOLUME ["/etc/stalwart", "/var/lib/stalwart"] VOLUME ["/etc/inbuxa", "/var/lib/inbuxa"]
EXPOSE 443 25 110 587 465 143 993 995 4190 8080 EXPOSE 443 25 110 587 465 143 993 995 4190 8080
ENV STALWART_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live ENV INBUXA_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$STALWART_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1 CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$INBUXA_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1
ENTRYPOINT ["/usr/local/bin/stalwart"] ENTRYPOINT ["/usr/local/bin/inbuxa"]
CMD ["--config", "/etc/stalwart/config.json"] CMD ["--config", "/etc/inbuxa/config.json"]
+32 -32
View File
@@ -108,7 +108,7 @@ RUN \
--mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,target=/usr/local/cargo/git \
source /env-cargo && \ source /env-cargo && \
if [ ! -z "${FDB_ARCH}" ]; then \ if [ ! -z "${FDB_ARCH}" ]; then \
RUSTFLAGS="-L /usr/lib" cargo chef cook --recipe-path recipe.json --zigbuild --release --target ${TARGET} -p stalwart --no-default-features --features "foundationdb s3 redis nats"; \ RUSTFLAGS="-L /usr/lib" cargo chef cook --recipe-path recipe.json --zigbuild --release --target ${TARGET} -p inbuxa --no-default-features --features "foundationdb s3 redis nats"; \
fi fi
RUN \ RUN \
--mount=type=secret,id=ACTIONS_RESULTS_URL,env=ACTIONS_RESULTS_URL \ --mount=type=secret,id=ACTIONS_RESULTS_URL,env=ACTIONS_RESULTS_URL \
@@ -116,7 +116,7 @@ RUN \
--mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,target=/usr/local/cargo/git \
source /env-cargo && \ source /env-cargo && \
cargo chef cook --recipe-path recipe.json --zigbuild --release --target ${TARGET} -p stalwart --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" cargo chef cook --recipe-path recipe.json --zigbuild --release --target ${TARGET} -p inbuxa --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats"
# Copy the source code # Copy the source code
COPY . . COPY . .
ENV RUSTC_WRAPPER="sccache" \ ENV RUSTC_WRAPPER="sccache" \
@@ -129,8 +129,8 @@ RUN \
--mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,target=/usr/local/cargo/git \
source /env-cargo && \ source /env-cargo && \
if [ ! -z "${FDB_ARCH}" ]; then \ if [ ! -z "${FDB_ARCH}" ]; then \
RUSTFLAGS="-L /usr/lib" cargo zigbuild --release --target ${TARGET} -p stalwart --no-default-features --features "foundationdb s3 redis nats" && \ RUSTFLAGS="-L /usr/lib" cargo zigbuild --release --target ${TARGET} -p inbuxa --no-default-features --features "foundationdb s3 redis nats" && \
mv /app/target/${TARGET}/release/stalwart /app/artifact/stalwart-foundationdb; \ mv /app/target/${TARGET}/release/inbuxa /app/artifact/inbuxa-foundationdb; \
fi fi
# Build generic version # Build generic version
RUN \ RUN \
@@ -139,8 +139,8 @@ RUN \
--mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,target=/usr/local/cargo/git \
source /env-cargo && \ source /env-cargo && \
cargo zigbuild --release --target ${TARGET} -p stalwart --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" && \ cargo zigbuild --release --target ${TARGET} -p inbuxa --no-default-features --features "sqlite postgres mysql rocks s3 redis azure nats" && \
mv /app/target/${TARGET}/release/stalwart /app/artifact/stalwart mv /app/target/${TARGET}/release/inbuxa /app/artifact/inbuxa
# ***************** # *****************
# Binary stage # Binary stage
@@ -156,21 +156,21 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \ apt-get update && \
apt-get install -yq --no-install-recommends ca-certificates curl tzdata libcap2-bin && \ apt-get install -yq --no-install-recommends ca-certificates curl tzdata libcap2-bin && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
groupadd -r -g 2000 stalwart && \ groupadd -r -g 2000 inbuxa && \
useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M stalwart && \ useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M inbuxa && \
mkdir -p /etc/stalwart /var/lib/stalwart && \ mkdir -p /etc/inbuxa /var/lib/inbuxa && \
chown stalwart:stalwart /etc/stalwart /var/lib/stalwart chown inbuxa:inbuxa /etc/inbuxa /var/lib/inbuxa
COPY --from=builder --chmod=0755 /app/artifact/stalwart /usr/local/bin/stalwart COPY --from=builder --chmod=0755 /app/artifact/inbuxa /usr/local/bin/inbuxa
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/stalwart RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/inbuxa
USER stalwart USER inbuxa
WORKDIR /var/lib/stalwart WORKDIR /var/lib/inbuxa
VOLUME ["/etc/stalwart", "/var/lib/stalwart"] VOLUME ["/etc/inbuxa", "/var/lib/inbuxa"]
EXPOSE 443 25 110 587 465 143 993 995 4190 8080 EXPOSE 443 25 110 587 465 143 993 995 4190 8080
ENV STALWART_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live ENV INBUXA_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$STALWART_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1 CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$INBUXA_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1
ENTRYPOINT ["/usr/local/bin/stalwart"] ENTRYPOINT ["/usr/local/bin/inbuxa"]
CMD ["--config", "/etc/stalwart/config.json"] CMD ["--config", "/etc/inbuxa/config.json"]
# ***************** # *****************
# Runtime image for musl targets # Runtime image for musl targets
@@ -178,18 +178,18 @@ CMD ["--config", "/etc/stalwart/config.json"]
FROM --platform=$TARGETPLATFORM alpine AS musl FROM --platform=$TARGETPLATFORM alpine AS musl
RUN apk add --update --no-cache ca-certificates curl tzdata libcap && \ RUN apk add --update --no-cache ca-certificates curl tzdata libcap && \
rm -rf /var/cache/apk/* && \ rm -rf /var/cache/apk/* && \
addgroup -S -g 2000 stalwart && \ addgroup -S -g 2000 inbuxa && \
adduser -S -D -H -u 2000 -G stalwart -s /sbin/nologin stalwart && \ adduser -S -D -H -u 2000 -G inbuxa -s /sbin/nologin inbuxa && \
mkdir -p /etc/stalwart /var/lib/stalwart && \ mkdir -p /etc/inbuxa /var/lib/inbuxa && \
chown stalwart:stalwart /etc/stalwart /var/lib/stalwart chown inbuxa:inbuxa /etc/inbuxa /var/lib/inbuxa
COPY --from=builder --chmod=0755 /app/artifact/stalwart /usr/local/bin/stalwart COPY --from=builder --chmod=0755 /app/artifact/inbuxa /usr/local/bin/inbuxa
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/stalwart RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/inbuxa
USER stalwart USER inbuxa
WORKDIR /var/lib/stalwart WORKDIR /var/lib/inbuxa
VOLUME ["/etc/stalwart", "/var/lib/stalwart"] VOLUME ["/etc/inbuxa", "/var/lib/inbuxa"]
EXPOSE 443 25 110 587 465 143 993 995 4190 8080 EXPOSE 443 25 110 587 465 143 993 995 4190 8080
ENV STALWART_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live ENV INBUXA_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$STALWART_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1 CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$INBUXA_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1
ENTRYPOINT ["/usr/local/bin/stalwart"] ENTRYPOINT ["/usr/local/bin/inbuxa"]
CMD ["--config", "/etc/stalwart/config.json"] CMD ["--config", "/etc/inbuxa/config.json"]
+14 -14
View File
@@ -53,28 +53,28 @@ COPY Cargo.lock .
COPY crates/ crates/ COPY crates/ crates/
COPY resources/ resources/ COPY resources/ resources/
COPY tests/ tests/ COPY tests/ tests/
RUN cargo build -p stalwart --no-default-features --features "foundationdb s3 redis azure nats" --release RUN cargo build -p inbuxa --no-default-features --features "foundationdb s3 redis azure nats" --release
FROM debian:trixie-slim AS runtime FROM debian:trixie-slim AS runtime
COPY --from=builder --chmod=0755 /app/target/release/stalwart /usr/local/bin/stalwart COPY --from=builder --chmod=0755 /app/target/release/inbuxa /usr/local/bin/inbuxa
COPY --from=builder /usr/lib/libfdb_c.so /usr/lib/libfdb_c.so COPY --from=builder /usr/lib/libfdb_c.so /usr/lib/libfdb_c.so
RUN export DEBIAN_FRONTEND=noninteractive && \ RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \ apt-get update && \
apt-get install -yq --no-install-recommends ca-certificates curl libcap2-bin && \ apt-get install -yq --no-install-recommends ca-certificates curl libcap2-bin && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
groupadd -r -g 2000 stalwart && \ groupadd -r -g 2000 inbuxa && \
useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M stalwart && \ useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M inbuxa && \
mkdir -p /etc/stalwart /var/lib/stalwart && \ mkdir -p /etc/inbuxa /var/lib/inbuxa && \
chown stalwart:stalwart /etc/stalwart /var/lib/stalwart && \ chown inbuxa:inbuxa /etc/inbuxa /var/lib/inbuxa && \
setcap 'cap_net_bind_service=+ep' /usr/local/bin/stalwart setcap 'cap_net_bind_service=+ep' /usr/local/bin/inbuxa
USER stalwart USER inbuxa
WORKDIR /var/lib/stalwart WORKDIR /var/lib/inbuxa
VOLUME ["/etc/stalwart", "/var/lib/stalwart"] VOLUME ["/etc/inbuxa", "/var/lib/inbuxa"]
EXPOSE 443 25 110 587 465 143 993 995 4190 8080 EXPOSE 443 25 110 587 465 143 993 995 4190 8080
ENV STALWART_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live ENV INBUXA_HEALTHCHECK_URL=https://127.0.0.1:443/healthz/live
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$STALWART_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1 CMD curl -fsSk -H "X-Forwarded-For: 127.0.0.1" "$INBUXA_HEALTHCHECK_URL" || curl -fsS -H "X-Forwarded-For: 127.0.0.1" http://127.0.0.1:8080/healthz/live || exit 1
ENTRYPOINT ["/usr/local/bin/stalwart"] ENTRYPOINT ["/usr/local/bin/inbuxa"]
CMD ["--config", "/etc/stalwart/config.json"] CMD ["--config", "/etc/inbuxa/config.json"]
+7 -3
View File
@@ -45,11 +45,15 @@ The report for every import is in `docs/fork/strip-reports/`. See
## Building ## Building
```bash ```bash
cargo build --release -p stalwart cargo build --release -p inbuxa # the binary is target/release/inbuxa
docker build -t inbuxa . # or the container image
``` ```
The binary and package are still named `stalwart` while the packaging is Settings are read from `INBUXA_*` environment variables. An existing Stalwart
reworked. install's `STALWART_*` variables still work, with a warning to rename them.
New installs keep their data in `/var/lib/inbuxa` and logs in
`/var/log/inbuxa`. Existing installs keep the paths their configuration
already names, so none of their data moves.
## License and credits ## License and credits
+1 -1
View File
@@ -69,7 +69,7 @@ impl Listeners {
bind: Map::new(vec![ bind: Map::new(vec![
SocketAddr::from_str(&format!( SocketAddr::from_str(&format!(
"[::]:{}", "[::]:{}",
std::env::var("STALWART_RECOVERY_MODE_PORT") types::branding::env_var("RECOVERY_MODE_PORT")
.ok() .ok()
.and_then(|p| p.parse::<u16>().ok()) .and_then(|p| p.parse::<u16>().ok())
.unwrap_or(8080) .unwrap_or(8080)
+1 -1
View File
@@ -503,7 +503,7 @@ impl Tracers {
} }
} else { } else {
// Add default tracer if none were found // Add default tracer if none were found
let level = std::env::var("STALWART_RECOVERY_MODE_LOG_LEVEL") let level = types::branding::env_var("RECOVERY_MODE_LOG_LEVEL")
.ok() .ok()
.and_then(|level| Level::from_str(&level).ok()) .and_then(|level| Level::from_str(&level).ok())
.unwrap_or(Level::Info); .unwrap_or(Level::Info);
+1 -1
View File
@@ -43,7 +43,7 @@ const HELP: &str = concat!(
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
r#" r#"
Usage: stalwart [OPTIONS] Usage: inbuxa [OPTIONS]
Options: Options:
-c, --config <PATH> Start server with the specified configuration file -c, --config <PATH> Start server with the specified configuration file
+1 -1
View File
@@ -529,7 +529,7 @@ async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> {
ansi: false, ansi: false,
prefix: "stalwart.log".into(), prefix: "stalwart.log".into(),
rotate: LogRotateFrequency::Daily, rotate: LogRotateFrequency::Daily,
path: "/var/log/stalwart".into(), path: "/var/log/inbuxa".into(),
..Default::default() ..Default::default()
}) })
.into(), .into(),
@@ -657,7 +657,7 @@ fn map_dns_server(dns_server: &DnsServerBootstrap) -> Option<registry::schema::s
const DEFAULT_DATA_PATH: &str = if cfg!(target_os = "freebsd") { const DEFAULT_DATA_PATH: &str = if cfg!(target_os = "freebsd") {
"/var/db/stalwart/" "/var/db/stalwart/"
} else { } else {
"/var/lib/stalwart/" "/var/lib/inbuxa/"
}; };
fn build_default_bootstrap(server: &Server) -> Bootstrap { fn build_default_bootstrap(server: &Server) -> Bootstrap {
@@ -676,7 +676,7 @@ fn build_default_bootstrap(server: &Server) -> Bootstrap {
in_memory_store: InMemoryStore::Default, in_memory_store: InMemoryStore::Default,
directory: DirectoryBootstrap::Internal, directory: DirectoryBootstrap::Internal,
tracer: Tracer::Log(TracerLog { tracer: Tracer::Log(TracerLog {
path: "/var/log/stalwart/".to_string(), path: "/var/log/inbuxa/".to_string(),
prefix: "stalwart".to_string(), prefix: "stalwart".to_string(),
ansi: true, ansi: true,
enable: true, enable: true,
+5 -5
View File
@@ -1,17 +1,17 @@
[package] [package]
name = "stalwart" name = "inbuxa"
description = "INBUXA Mail and Collaboration Server, a fork of Stalwart" description = "INBUXA Mail and Collaboration Server, a fork of Stalwart"
authors = [ "Stalwart Labs LLC <[email protected]>"] authors = [ "Stalwart Labs LLC <[email protected]>"]
repository = "https://github.com/stalwartlabs/stalwart" homepage = "https://inbuxa.org"
homepage = "https://stalw.art"
keywords = ["imap", "jmap", "smtp", "email", "mail", "webdav", "server"] keywords = ["imap", "jmap", "smtp", "email", "mail", "webdav", "server"]
categories = ["email"] categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL" # Upstream offers AGPL-3.0-only OR LicenseRef-SEL; INBUXA takes the AGPL only.
license = "AGPL-3.0-only"
version = "0.16.22" version = "0.16.22"
edition = "2024" edition = "2024"
[[bin]] [[bin]]
name = "stalwart" name = "inbuxa"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
+2 -2
View File
@@ -4143,7 +4143,7 @@ impl Default for Bootstrap {
request_tls_certificate: true, request_tls_certificate: true,
generate_dkim_keys: true, generate_dkim_keys: true,
data_store: DataStore::RocksDb(RocksDbStore { data_store: DataStore::RocksDb(RocksDbStore {
path: "/var/lib/stalwart/".to_string(), path: "/var/lib/inbuxa/".to_string(),
..Default::default() ..Default::default()
}), }),
blob_store: BlobStore::Default, blob_store: BlobStore::Default,
@@ -4151,7 +4151,7 @@ impl Default for Bootstrap {
in_memory_store: InMemoryStore::Default, in_memory_store: InMemoryStore::Default,
directory: DirectoryBootstrap::Internal, directory: DirectoryBootstrap::Internal,
tracer: Tracer::Log(TracerLog { tracer: Tracer::Log(TracerLog {
path: "/var/log/stalwart/".to_string(), path: "/var/log/inbuxa/".to_string(),
..Default::default() ..Default::default()
}), }),
dns_server: DnsServerBootstrap::Manual, dns_server: DnsServerBootstrap::Manual,
+1 -1
View File
@@ -1051,7 +1051,7 @@ impl<T: SessionStream> Session<T> {
} }
headers.extend_from_slice(b"by "); headers.extend_from_slice(b"by ");
headers.extend_from_slice(self.hostname.as_bytes()); headers.extend_from_slice(self.hostname.as_bytes());
headers.extend_from_slice(b" (Stalwart SMTP) with "); headers.extend_from_slice(concat!(" (", types::brand!(), " SMTP) with ").as_bytes());
headers.extend_from_slice(match (self.stream.is_tls(), !self.is_authenticated()) { headers.extend_from_slice(match (self.stream.is_tls(), !self.is_authenticated()) {
(true, true) => b"ESMTPS", (true, true) => b"ESMTPS",
(true, false) => b"ESMTPSA", (true, false) => b"ESMTPSA",
+3 -3
View File
@@ -31,18 +31,18 @@ impl RegistryStore {
.collect::<String>(); .collect::<String>();
eprintln!(); eprintln!();
eprintln!("════════════════════════════════════════════════════════════"); eprintln!("════════════════════════════════════════════════════════════");
eprintln!("🔑 Stalwart bootstrap mode - temporary administrator account"); eprintln!("🔑 INBUXA bootstrap mode - temporary administrator account");
eprintln!(); eprintln!();
eprintln!(" username: admin"); eprintln!(" username: admin");
eprintln!(" password: {password}"); eprintln!(" password: {password}");
eprintln!(); eprintln!();
eprintln!("Use these credentials to complete the initial setup at the"); eprintln!("Use these credentials to complete the initial setup at the");
eprintln!("/admin web UI. Once setup is done, Stalwart will provision a"); eprintln!("/admin web UI. Once setup is done, the server will provision a");
eprintln!("permanent administrator and this temporary account will no"); eprintln!("permanent administrator and this temporary account will no");
eprintln!("longer apply."); eprintln!("longer apply.");
eprintln!(); eprintln!();
eprintln!("This password is shown only once. To pin a credential"); eprintln!("This password is shown only once. To pin a credential");
eprintln!("instead, set STALWART_RECOVERY_ADMIN=admin:<password> in the"); eprintln!("instead, set INBUXA_RECOVERY_ADMIN=admin:<password> in the");
eprintln!("env file."); eprintln!("env file.");
eprintln!("════════════════════════════════════════════════════════════"); eprintln!("════════════════════════════════════════════════════════════");
eprintln!(); eprintln!();
+7 -7
View File
@@ -17,7 +17,7 @@ pub(crate) enum RegistryInit {
impl RegistryStoreInner { impl RegistryStoreInner {
pub(crate) fn new(local_path: PathBuf) -> Self { pub(crate) fn new(local_path: PathBuf) -> Self {
let env_hostname = std::env::var("STALWART_HOSTNAME") let env_hostname = types::branding::env_var("HOSTNAME")
.ok() .ok()
.filter(|h| !h.is_empty()) .filter(|h| !h.is_empty())
.unwrap_or_else(|| { .unwrap_or_else(|| {
@@ -35,30 +35,30 @@ impl RegistryStoreInner {
store: Store::None, store: Store::None,
id_generator: SnowflakeIdGenerator::new(), id_generator: SnowflakeIdGenerator::new(),
node_id: 0, node_id: 0,
env_recovery_mode: std::env::var("STALWART_RECOVERY_MODE") env_recovery_mode: types::branding::env_var("RECOVERY_MODE")
.ok() .ok()
.map(|v| v == "1" || v.eq_ignore_ascii_case("true")) .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false), .unwrap_or(false),
env_recovery_admin: std::env::var("STALWART_RECOVERY_ADMIN") env_recovery_admin: types::branding::env_var("RECOVERY_ADMIN")
.ok() .ok()
.and_then(|v| { .and_then(|v| {
v.split_once(':') v.split_once(':')
.map(|(a, p)| (a.trim().to_string(), p.trim().to_string())) .map(|(a, p)| (a.trim().to_string(), p.trim().to_string()))
}) })
.filter(|(a, p)| !a.is_empty() && !p.is_empty()), .filter(|(a, p)| !a.is_empty() && !p.is_empty()),
env_cluster_role: std::env::var("STALWART_ROLE") env_cluster_role: types::branding::env_var("ROLE")
.ok() .ok()
.filter(|r| !r.is_empty()), .filter(|r| !r.is_empty()),
env_push_shard_id: std::env::var("STALWART_PUSH_SHARD") env_push_shard_id: types::branding::env_var("PUSH_SHARD")
.ok() .ok()
.and_then(|id| id.parse::<u32>().ok().and_then(|v| v.checked_sub(1))) .and_then(|id| id.parse::<u32>().ok().and_then(|v| v.checked_sub(1)))
.unwrap_or(0), .unwrap_or(0),
env_public_url: std::env::var("STALWART_PUBLIC_URL") env_public_url: types::branding::env_var("PUBLIC_URL")
.ok() .ok()
.map(|v| v.trim().trim_end_matches('/').to_string()) .map(|v| v.trim().trim_end_matches('/').to_string())
.filter(|u| !u.is_empty()) .filter(|u| !u.is_empty())
.or_else(|| { .or_else(|| {
std::env::var("STALWART_HTTPS_PORT").ok().and_then(|p| { types::branding::env_var("HTTPS_PORT").ok().and_then(|p| {
p.parse::<u16>() p.parse::<u16>()
.ok() .ok()
.map(|port| format!("https://{}:{}", env_hostname, port)) .map(|port| format!("https://{}:{}", env_hostname, port))
+19
View File
@@ -45,3 +45,22 @@ macro_rules! brand_url {
"https://inbuxa.org" "https://inbuxa.org"
}; };
} }
/// Reads one of the server's environment variables by its unprefixed name,
/// such as `RECOVERY_ADMIN`.
///
/// `INBUXA_<name>` wins. `STALWART_<name>` is still read when the new name
/// isn't set, so an existing Stalwart install moves over without editing its
/// environment, and a warning says which variable to rename.
pub fn env_var(name: &str) -> Result<String, std::env::VarError> {
match std::env::var(format!("INBUXA_{name}")) {
Err(std::env::VarError::NotPresent) => {
let legacy = std::env::var(format!("STALWART_{name}"));
if legacy.is_ok() {
eprintln!("Warning: STALWART_{name} is deprecated; set INBUXA_{name} instead.");
}
legacy
}
found => found,
}
}
+1 -1
View File
@@ -145,7 +145,7 @@ pub fn build_http_client(
allow_invalid_certs: bool, allow_invalid_certs: bool,
) -> Result<Client, String> { ) -> Result<Client, String> {
let mut headers = build_http_headers(raw_headers, username, password, token, content_type)?; let mut headers = build_http_headers(raw_headers, username, password, token, content_type)?;
headers.insert(USER_AGENT, "Stalwart/1.0.0".parse().unwrap()); headers.insert(USER_AGENT, "INBUXA/1.0.0".parse().unwrap()); // types::brand!(); utils does not depend on types
match http_client_builder(allow_invalid_certs) match http_client_builder(allow_invalid_certs)
.connect_timeout(timeout) .connect_timeout(timeout)
+32 -4
View File
@@ -103,10 +103,12 @@ repository. Instead:
- Each import's full strip report is committed on `main` under - Each import's full strip report is committed on `main` under
`docs/fork/strip-reports/<ref>.md` (and `.json`), beside the merge that `docs/fork/strip-reports/<ref>.md` (and `.json`), beside the merge that
brought the release in. brought the release in.
- The snapshot includes upstream's `.github/` workflows, release automation - The snapshot includes upstream's `.github/`: its CI and release workflows,
included. They're kept on `upstream` as upstream shipped them, but must be workflows that auto-close issues and PRs from anyone not on its allowlist,
disabled or replaced on `main` before the repository is ever pushed issue templates and Dependabot. On `main` the whole directory is moved to
anywhere that runs them. `.github-upstream/`, so GitHub never runs it. Upstream changes to it still
merge there on each sync. INBUXA writes its own `.github/` when the
repository is first published.
### 2.2b What the first import proved (v0.16.22, 2026-09-18) ### 2.2b What the first import proved (v0.16.22, 2026-09-18)
@@ -179,6 +181,32 @@ one edition.
interoperability, not branding, and renaming them breaks every existing interoperability, not branding, and renaming them breaks every existing
client. Anything the fork adds uses its own namespace (open: which one). client. Anything the fork adds uses its own namespace (open: which one).
### 2.5 Packaging
Done 2026-09-18:
- The package and binary are `inbuxa` (`cargo build -p inbuxa`). The binary's
help, banner and every protocol greeting say INBUXA (the branding module,
`types::brand!()`).
- Settings come from `INBUXA_*` environment variables. Each still falls back
to its `STALWART_*` name, with a startup warning to rename it
(`types::branding::env_var`). That covers all nine the server reads:
`HOSTNAME`, `RECOVERY_MODE`, `RECOVERY_ADMIN`, `RECOVERY_MODE_PORT`,
`RECOVERY_MODE_LOG_LEVEL`, `ROLE`, `PUSH_SHARD`, `PUBLIC_URL`, `HTTPS_PORT`.
- **Not renamed, on purpose:** `STALWART_APP_` and the two `STALWART_SPAM_...`
names. They look like environment variables, but they're keys inside the
data store, so renaming them would orphan existing installed apps and
spam-classifier models.
- New installs default to `/var/lib/inbuxa` for data and `/var/log/inbuxa` for
logs. Existing installs keep the paths their configuration names, so no data
moves.
- The container image runs as user `inbuxa` (uid 2000, as upstream), with
`/etc/inbuxa` and `/var/lib/inbuxa` as volumes, `INBUXA_HEALTHCHECK_URL`, and
`inbuxa --config /etc/inbuxa/config.json`. The systemd unit
(`inbuxa.service`), launchd plist and AppArmor profile are renamed to match.
- `install.sh` is a stub that says there's no release yet. Upstream's version
would download and install Stalwart itself.
## 3. Clean room ## 3. Clean room
INBUXA runs on a paid Stalwart Enterprise license, so its maintainer is a INBUXA runs on a paid Stalwart Enterprise license, so its maintainer is a
+10 -1067
View File
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
#include <tunables/global> #include <tunables/global>
profile stalwart flags=(attach_disconnected) { profile inbuxa flags=(attach_disconnected) {
#include <abstractions/base> #include <abstractions/base>
#include <abstractions/nameservice> #include <abstractions/nameservice>
#include <abstractions/openssl> #include <abstractions/openssl>
@@ -17,8 +17,8 @@ profile stalwart flags=(attach_disconnected) {
owner /proc/*/net/if_inet6 r, owner /proc/*/net/if_inet6 r,
owner /proc/*/net/ipv6_route r, owner /proc/*/net/ipv6_route r,
# Full write access to /opt/stalwart # Full write access to /opt/inbuxa
/opt/stalwart/** rwk, /opt/inbuxa/** rwk,
# Allow creating directories under /tmp # Allow creating directories under /tmp
/tmp/ r, /tmp/ r,
@@ -51,9 +51,9 @@ profile stalwart flags=(attach_disconnected) {
network inet6 dgram bind port 7911, network inet6 dgram bind port 7911,
# Basic system access # Basic system access
/usr/bin/stalwart rix, /usr/bin/inbuxa rix,
/etc/stalwart/** r, /etc/inbuxa/** r,
/var/log/stalwart/** w, /var/log/inbuxa/** w,
# Additional permissions might be needed depending on specific requirements # Additional permissions might be needed depending on specific requirements
} }
@@ -4,12 +4,12 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Label</key> <key>Label</key>
<string>stalwart.mail</string> <string>inbuxa.mail</string>
<key>ServiceDescription</key> <key>ServiceDescription</key>
<string>Stalwart</string> <string>INBUXA</string>
<key>ProgramArguments</key> <key>ProgramArguments</key>
<array> <array>
<string>__PATH__/bin/stalwart</string> <string>__PATH__/bin/inbuxa</string>
<string>--config=__PATH__/etc/config.json</string> <string>--config=__PATH__/etc/config.json</string>
</array> </array>
<key>RunAtLoad</key> <key>RunAtLoad</key>
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Stalwart Server Description=INBUXA Server
Conflicts=postfix.service sendmail.service exim4.service Conflicts=postfix.service sendmail.service exim4.service
ConditionPathExists=__PATH__/etc/config.json ConditionPathExists=__PATH__/etc/config.json
After=network-online.target After=network-online.target
@@ -11,10 +11,10 @@ KillMode=process
KillSignal=SIGINT KillSignal=SIGINT
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.json ExecStart=__PATH__/bin/inbuxa --config=__PATH__/etc/config.json
SyslogIdentifier=stalwart SyslogIdentifier=inbuxa
User=stalwart User=inbuxa
Group=stalwart Group=inbuxa
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install] [Install]