Веб-почта по HTTPS на том же адресе: https://mail.jambocheck.com (порт 443)
continuous-integration/drone/pr Build is passing

HTTPS_PORT — второй Netty-сервер с тем же HttpHandler и тем же сертификатом, что SMTP/IMAP
(Let's Encrypt от acme, перечитывается при продлении). HSTS добавляет Spring Security.
docker-compose.public.yml: 443 → 8443. HTTP 8080 остаётся для прокси внутри сети.
Тесты: HttpsConfigTest, всего 74; живой запуск: /api/config по HTTPS, HSTS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 02:18:42 +01:00
co-authored by Claude Opus 5.5
parent 4ad2e44e9b
commit f9b929ac7a
5 changed files with 134 additions and 2 deletions
@@ -0,0 +1,81 @@
package com.mailserver.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import java.time.Duration;
/**
* The web client and API over HTTPS on the mail host itself (mailserver.https.port, in public compose 8443 →
* published 443): https://mail.jambocheck.com opens the web mail, no second name through Jambotron's Caddy.
* Same certificate as SMTP/IMAP (Let's Encrypt from the acme service, reloaded when renewed). The plain HTTP port
* (8080) stays for a reverse proxy inside the network (staging: domain in Jambotron).
*/
@Configuration
public class HttpsConfig {
@Bean
@ConditionalOnExpression("${mailserver.https.port:0} > 0 and ${mailserver.tls.enabled:true}")
public HttpsServer httpsServer(HttpHandler handler, MailServerConfig.SmtpTlsContext tls,
@Value("${mailserver.smtp.host:0.0.0.0}") String host,
@Value("${mailserver.https.port}") int port) {
return new HttpsServer(handler, tls, host, port);
}
public static class HttpsServer implements SmartLifecycle {
private static final Logger log = LoggerFactory.getLogger(HttpsServer.class);
private final HttpHandler handler;
private final MailServerConfig.SmtpTlsContext tls;
private final String host;
private final int port;
private volatile DisposableServer server;
HttpsServer(HttpHandler handler, MailServerConfig.SmtpTlsContext tls, String host, int port) {
this.handler = handler;
this.tls = tls;
this.host = host;
this.port = port;
}
@Override
public void start() {
server = HttpServer.create()
.host(host)
.port(port)
.secure(spec -> spec.sslContext(tls.context()))
// HSTS: Spring Security adds it for HTTPS requests by itself
.handle(new ReactorHttpHandlerAdapter(handler))
.bindNow(Duration.ofSeconds(30));
log.info("HTTPS (web client + API) listening on {}:{}", host, server.port());
}
@Override
public void stop() {
DisposableServer current = server;
if (current != null) {
current.disposeNow(Duration.ofSeconds(10));
server = null;
}
}
@Override
public boolean isRunning() {
return server != null;
}
public int port() {
return server == null ? -1 : server.port();
}
}
}