1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-18 14:47:03 +00:00
Files
go-chasquid-smtp/internal/config/config.go
Alberto Bertogli 24c2c4f5fd Make the max queue size and give up time configurable
Today, the maximum number of items in the queue, as well as how long we
keep attempting to send each item, is hard-coded and not changed by end
users.

While they are totally adequate for chasquid's main use cases, it can
still be useful for some users to change them.

So this patch adds two new configuration options for those settings.
They're marked experimental for now, so we can adjust them if needed
after they get more exposure.

Thanks to Lewis Ross-Jones <lewis_r_j@hotmail.com> for suggesting this
improvement, and help with testing it.
2025-06-07 11:00:00 +01:00

183 lines
4.8 KiB
Go

// Package config implements the chasquid configuration.
package config
// Generate the config protobuf.
//go:generate protoc --go_out=. --go_opt=paths=source_relative --experimental_allow_proto3_optional config.proto
import (
"fmt"
"os"
"time"
"blitiri.com.ar/go/log"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
)
var defaultConfig = &Config{
MaxDataSizeMb: 50,
SmtpAddress: []string{"systemd"},
SubmissionAddress: []string{"systemd"},
SubmissionOverTlsAddress: []string{"systemd"},
MailDeliveryAgentBin: "maildrop",
MailDeliveryAgentArgs: []string{"-f", "%from%", "-d", "%to_user%"},
DataDir: "/var/lib/chasquid",
SuffixSeparators: proto.String("+"),
DropCharacters: proto.String("."),
MailLogPath: "<syslog>",
MaxQueueItems: 200,
GiveUpSendAfter: "20h",
}
// Load the config from the given file, with the given overrides.
func Load(path, overrides string) (*Config, error) {
// Start with a copy of the default config.
c := proto.Clone(defaultConfig).(*Config)
// Load from the path.
buf, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config at %q: %v", path, err)
}
fromFile := &Config{}
err = prototext.Unmarshal(buf, fromFile)
if err != nil {
return nil, fmt.Errorf("parsing config: %v", err)
}
override(c, fromFile)
// Handle command line overrides.
fromOverrides := &Config{}
err = prototext.Unmarshal([]byte(overrides), fromOverrides)
if err != nil {
return nil, fmt.Errorf("parsing override: %v", err)
}
override(c, fromOverrides)
// Handle hostname separate, because if it is set, we don't need to call
// os.Hostname which can fail.
if c.Hostname == "" {
c.Hostname, err = os.Hostname()
if err != nil {
return nil, fmt.Errorf("could not get hostname: %v", err)
}
}
// Validate the GiveUpSendAfter value.
if _, err := time.ParseDuration(c.GiveUpSendAfter); err != nil {
return nil, fmt.Errorf(
"invalid give_up_send_after value %q: %v", c.GiveUpSendAfter, err)
}
return c, nil
}
// Override fields in `c` that are set in `o`. We can't use proto.Merge
// because the semantics would not be convenient for overriding.
func override(c, o *Config) {
if o.Hostname != "" {
c.Hostname = o.Hostname
}
if o.MaxDataSizeMb > 0 {
c.MaxDataSizeMb = o.MaxDataSizeMb
}
if len(o.SmtpAddress) > 0 {
c.SmtpAddress = o.SmtpAddress
}
if len(o.SubmissionAddress) > 0 {
c.SubmissionAddress = o.SubmissionAddress
}
if len(o.SubmissionOverTlsAddress) > 0 {
c.SubmissionOverTlsAddress = o.SubmissionOverTlsAddress
}
if o.MonitoringAddress != "" {
c.MonitoringAddress = o.MonitoringAddress
}
if o.MailDeliveryAgentBin != "" {
c.MailDeliveryAgentBin = o.MailDeliveryAgentBin
}
if len(o.MailDeliveryAgentArgs) > 0 {
c.MailDeliveryAgentArgs = o.MailDeliveryAgentArgs
}
if o.DataDir != "" {
c.DataDir = o.DataDir
}
if o.SuffixSeparators != nil {
c.SuffixSeparators = o.SuffixSeparators
}
if o.DropCharacters != nil {
c.DropCharacters = o.DropCharacters
}
if o.MailLogPath != "" {
c.MailLogPath = o.MailLogPath
}
if o.DovecotAuth {
c.DovecotAuth = true
}
if o.DovecotUserdbPath != "" {
c.DovecotUserdbPath = o.DovecotUserdbPath
}
if o.DovecotClientPath != "" {
c.DovecotClientPath = o.DovecotClientPath
}
if o.HaproxyIncoming {
c.HaproxyIncoming = true
}
if o.MaxQueueItems > 0 {
c.MaxQueueItems = o.MaxQueueItems
}
if o.GiveUpSendAfter != "" {
c.GiveUpSendAfter = o.GiveUpSendAfter
}
}
// LogConfig logs the given configuration, in a human-friendly way.
func LogConfig(c *Config) {
log.Infof("Configuration:")
log.Infof(" Hostname: %q", c.Hostname)
log.Infof(" Max data size (MB): %d", c.MaxDataSizeMb)
log.Infof(" SMTP Addresses: %q", c.SmtpAddress)
log.Infof(" Submission Addresses: %q", c.SubmissionAddress)
log.Infof(" Submission+TLS Addresses: %q", c.SubmissionOverTlsAddress)
log.Infof(" Monitoring address: %q", c.MonitoringAddress)
log.Infof(" MDA: %q %q", c.MailDeliveryAgentBin, c.MailDeliveryAgentArgs)
log.Infof(" Data directory: %q", c.DataDir)
if c.SuffixSeparators == nil {
log.Infof(" Suffix separators: nil")
} else {
log.Infof(" Suffix separators: %q", *c.SuffixSeparators)
}
if c.DropCharacters == nil {
log.Infof(" Drop characters: nil")
} else {
log.Infof(" Drop characters: %q", *c.DropCharacters)
}
log.Infof(" Mail log: %q", c.MailLogPath)
log.Infof(" Dovecot auth: %v (%q, %q)",
c.DovecotAuth, c.DovecotUserdbPath, c.DovecotClientPath)
log.Infof(" HAProxy incoming: %v", c.HaproxyIncoming)
log.Infof(" Max queue items: %d", c.MaxQueueItems)
log.Infof(" Give up send after: %s", c.GiveUpSendAfterDuration())
}
func (c *Config) GiveUpSendAfterDuration() time.Duration {
// We validate the string value at config load time, so we know it is well
// formed.
d, _ := time.ParseDuration(c.GiveUpSendAfter)
return d
}