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:
Generated
+32
-32
@@ -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
@@ -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
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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!();
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
+8
-1065
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]
|
||||||
Reference in New Issue
Block a user