Add optional inbound relay (backup-MX) behind INBOUND_RELAY_ENABLE.
test / test (push) Waiting to run

Port 25 accepts only configured domains and listed recipients, then forwards to an upstream; the outbound path is unchanged when the flag is off.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 23:17:30 +03:00
parent 6218540211
commit 0d98d92642
49 changed files with 2495 additions and 86 deletions
+19
View File
@@ -15,6 +15,11 @@ var documentedPublic = []string{
"PANEL_SESSION_IDLE_DAYS",
"SELFPOST_DNS_RESOLVERS",
"TRUSTED_PROXY_CIDR",
"INBOUND_RELAY_ENABLE",
"INBOUND_ANTISPAM_MILTER",
"INBOUND_ANTISPAM_MILTER_ACTION",
"INBOUND_RATE_LIMIT_MESSAGES_PER_IP",
"INBOUND_MESSAGE_SIZE_LIMIT",
}
// documentedInternal matches architecture.md § Configuration "Internal env vars".
@@ -34,6 +39,10 @@ var documentedInternal = []string{
"POSTFIX_DIR",
"POSTFIX_SENDER_LOGIN_MAPS",
"POSTFIX_QUEUE_DIR",
"POSTFIX_RELAY_DOMAINS",
"POSTFIX_TRANSPORT_MAPS",
"POSTFIX_RELAY_RECIPIENTS",
"POSTFIX_TLS_POLICY_MAPS",
"SELFPOST_DEPLOY_ROOT",
"MILTER_CONNECT_TIMEOUT",
"MILTER_COMMAND_TIMEOUT",
@@ -73,6 +82,7 @@ var loadConfigKeys = []string{
"SASL_REALM",
"POSTFIX_DIR",
"SELFPOST_DEPLOY_ROOT",
"INBOUND_RELAY_ENABLE",
}
// buildScriptKeys is every ${VAR:-…} / os.Getenv used in build/*.sh and entrypoint.sh
@@ -90,6 +100,15 @@ var buildScriptKeys = []string{
"POSTFIX_QUEUE_DIR",
"SASL_DB_PATH",
"SUBMISSION_ENABLE",
"INBOUND_RELAY_ENABLE",
"INBOUND_ANTISPAM_MILTER",
"INBOUND_ANTISPAM_MILTER_ACTION",
"INBOUND_RATE_LIMIT_MESSAGES_PER_IP",
"INBOUND_MESSAGE_SIZE_LIMIT",
"POSTFIX_RELAY_DOMAINS",
"POSTFIX_TRANSPORT_MAPS",
"POSTFIX_RELAY_RECIPIENTS",
"POSTFIX_TLS_POLICY_MAPS",
"MILTER_CONNECT_TIMEOUT",
"MILTER_COMMAND_TIMEOUT",
"MILTER_CONTENT_TIMEOUT",
+16 -6
View File
@@ -11,6 +11,7 @@ import (
"github.com/mixeme/selfpost/internal/app"
"github.com/mixeme/selfpost/internal/buildinfo"
"github.com/mixeme/selfpost/internal/domain"
"github.com/mixeme/selfpost/internal/inbound"
"github.com/mixeme/selfpost/internal/postfix"
"github.com/mixeme/selfpost/internal/store"
"github.com/mixeme/selfpost/internal/web"
@@ -19,10 +20,12 @@ import (
// mailStack is the panel's domain and application services plus the on-disk
// mail-path adapters they write through.
type mailStack struct {
Domains *domain.Service
Apps *app.Service
pf *postfix.Postfix
odk *domain.OpenDKIM
Domains *domain.Service
Apps *app.Service
Inbound *inbound.Service
pf *postfix.Postfix
odk *domain.OpenDKIM
inboundEnabled bool
}
func newMailStack(cfg config, st *store.Store) *mailStack {
@@ -30,7 +33,8 @@ func newMailStack(cfg config, st *store.Store) *mailStack {
odk := domain.NewOpenDKIM(cfg.opendkimDir)
apps := app.NewService(st, app.NewSASLDB(cfg.saslDBPath, cfg.saslRealm), pf)
domains := domain.NewService(st, odk, apps, cfg.dkimSelectorDef)
return &mailStack{Domains: domains, Apps: apps, pf: pf, odk: odk}
inb := inbound.NewService(st, pf)
return &mailStack{Domains: domains, Apps: apps, Inbound: inb, pf: pf, odk: odk, inboundEnabled: cfg.inboundEnabled}
}
// Resync rebuilds OpenDKIM's tables and Postfix's sender map from SQLite and
@@ -42,6 +46,11 @@ func (m *mailStack) Resync() error {
if err := m.Apps.Resync(); err != nil {
return fmt.Errorf("postfix resync: %w", err)
}
if m.inboundEnabled {
if err := m.Inbound.Resync(); err != nil {
return fmt.Errorf("inbound maps resync: %w", err)
}
}
return nil
}
@@ -70,7 +79,7 @@ func newPanel(cfg config, st *store.Store) (*web.Server, error) {
// effective config, including a manual override; the panel keeps this
// snapshot for the process lifetime (architecture.md).
retryPolicy := postfix.LoadRetryPolicy()
return web.New(st, ms.Domains, ms.Apps, web.Config{
return web.New(st, ms.Domains, ms.Apps, ms.Inbound, web.Config{
Hostname: cfg.hostname,
CookieSecure: cfg.cookieSecure,
SubmissionEnabled: cfg.submissionEnabled,
@@ -88,6 +97,7 @@ func newPanel(cfg config, st *store.Store) (*web.Server, error) {
RateLimitMessagesPerIP: cfg.rateLimitMessagesPerIP,
RateLimitWindowSeconds: cfg.rateLimitWindowSeconds,
RetryPolicy: retryPolicy,
InboundEnabled: cfg.inboundEnabled,
}, cfg.setupTokenPath)
}
+4
View File
@@ -79,6 +79,8 @@ type config struct {
saslRealm string
postfixDir string
deployRoot string
inboundEnabled bool
}
func loadConfig() config {
@@ -140,6 +142,8 @@ func loadConfig() config {
saslRealm: saslRealm(),
postfixDir: envDefault("POSTFIX_DIR", filepath.Join(dataDir, "postfix")),
deployRoot: envDefault("SELFPOST_DEPLOY_ROOT", "/selfpost-deploy"),
// Optional inbound relay (backup-MX / forwarder). Off unless exactly "true".
inboundEnabled: os.Getenv("INBOUND_RELAY_ENABLE") == "true",
}
}