mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-29 15:02:30 +00:00
bfb9694f0c
Many places where we return errors don't use the %w wrapping, because they predate that functionality. This patch changes those to use %w, so callers can inspect the underlying errors with errors.Is and errors.As if needed in the future. The text of the errors does not change, and no existing callers inspect these errors, so there are no behaviour changes.
183 lines
4.8 KiB
Go
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: new("+"),
|
|
DropCharacters: new("."),
|
|
|
|
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: %w", path, err)
|
|
}
|
|
|
|
fromFile := &Config{}
|
|
err = prototext.Unmarshal(buf, fromFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing config: %w", 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: %w", 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: %w", err)
|
|
}
|
|
}
|
|
|
|
// Validate the GiveUpSendAfter value.
|
|
if _, err := time.ParseDuration(c.GiveUpSendAfter); err != nil {
|
|
return nil, fmt.Errorf(
|
|
"invalid give_up_send_after value %q: %w", 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
|
|
}
|