Loads, validates, and stores the Sovite configuration file.
The file is TOML. Every key is checked against a schema: unknown keys,
wrong types, and invalid values are all reported together, each with the
path of the bad key. See docs/configuration.md for the reference.
The running configuration is kept in :persistent_term. Read it with
get/0.
Summary
Types
A DNS list for [screen], see Sovite.Abuse.DNSBL.
An [[ip_pool]]: see Sovite.Core.IPPools.
A rate limit: {count, window}, the window in milliseconds.
A [[tenant]]: see Sovite.Core.Tenants.
Functions
Returns whether any listener offers AUTH.
The SASL mechanisms to offer: auth.mechanisms, or by default those
the backend supports.
Returns the config file path: $SOVITE_CONFIG if set, otherwise
/etc/sovite/sovite.toml.
Forgets the configuration of a server instance that stopped.
Returns the running configuration. Raises if none has been stored.
Returns the running configuration of a server instance, or nil.
Reads, parses, and validates the config file at path.
Like load/1, with shared (TOML) as the shared configuration
instead of the file cluster.shared_config names: whether the server
could run with a new shared configuration.
Parses and validates TOML config contents.
Stores config as the running configuration.
Stores config as the running configuration of one server instance
(Sovite.Core.Supervisor), and as the running configuration. Parts
that read it on each use, such as the web server, see reloads at once.
The DNS resolver for config: Sovite.DNS.InetRes with the [dns]
nameservers, or the system's.
Returns whether TLS certificates are configured (files or ACME).
Validates a decoded config map (string keys, as produced by a TOML decoder), fills in defaults, and returns the config struct.
Types
A DNS list for [screen], see Sovite.Abuse.DNSBL.
@type ip_pool() :: %{ name: String.t(), overflow: String.t() | nil, address: [ %{ address: :inet.ip_address(), helo: String.t() | nil, node: String.t() | nil, warmup_start: Date.t() | nil, warmup: [pos_integer()], daily_limit: pos_integer() | nil } ] }
An [[ip_pool]]: see Sovite.Core.IPPools.
@type rate() :: {pos_integer(), pos_integer()}
A rate limit: {count, window}, the window in milliseconds.
@type t() :: %Sovite.Core.Config{ admin: %{ enabled: boolean(), address: :inet.ip_address(), port: :inet.port_number(), token_file: Path.t(), allowed_networks: [Sovite.Net.network()], tls: boolean(), public_metrics: boolean(), web_root: Path.t() | nil }, arc: %{ verify: boolean(), seal: boolean(), domain: String.t() | nil, selector: String.t() | nil, trusted_sealers: [String.t()] }, auth: map(), bounce: %{ double_bounce_recipient: String.t() | nil, size_limit: pos_integer() }, cluster: %{ enabled: boolean(), node_name: String.t() | nil, address: :inet.ip_address() | nil, port: :inet.port_number(), seeds: [String.t()], cert_file: Path.t() | nil, key_file: Path.t() | nil, ca_file: Path.t() | nil, join_interval: pos_integer(), sync_interval: pos_integer(), shared_config: Path.t() | nil, queue_replicas: 0..2, handover_after: pos_integer(), destination_affinity: boolean() }, database: %{ adapter: :sqlite | :postgres | :mysql, path: Path.t(), url: String.t() | nil, pool_size: pos_integer(), ssl: boolean() }, delivery: %{ relayhost: %{host: String.t(), port: :inet.port_number(), mx: boolean()} | nil, max_deliveries: pos_integer(), destination_concurrency: pos_integer(), destination_rate_delay: pos_integer() | nil, max_recipients: pos_integer(), max_addresses: pos_integer(), ip_versions: [:ipv6 | :ipv4, ...], connect_timeout: pos_integer(), tls: tls_level(), tls_policy: %{required(String.t()) => tls_level()}, tls_ca_file: Path.t() | nil, relayhost_username: String.t() | nil, relayhost_password: String.t() | nil, source_address: [:inet.ip_address()], ip_pool: String.t() | nil, connection_cache: boolean(), connection_cache_time: pos_integer(), chunking: boolean() }, dkim: %{ verify: boolean(), sign: boolean(), headers: [String.t()] | nil, expiration: pos_integer() | nil, key: [ %{ domain: String.t(), selector: String.t(), file: Path.t(), sign: boolean(), signing_key: Sovite.DKIM.SigningKey.t() | nil } ] }, dmarc: %{ verify: boolean(), policy: :report | :enforce, reports: boolean(), report_interval: pos_integer(), report_org: String.t(), report_from: String.t() }, dns: %{ nameservers: [:inet.ip_address()], port: :inet.port_number(), timeout: pos_integer(), dnssec: :auto | :on | :off }, domains: %{ local: [String.t()], relay: [String.t()], local_recipients: [String.t()] | nil, aliased: [String.t()], hosted: [String.t()] }, greylist: %{ enabled: boolean(), delay: pos_integer(), retry_window: pos_integer(), max_age: pos_integer() }, ip_pool: [ip_pool()], listener: [ %{ address: :inet.ip_address(), port: :inet.port_number(), mode: :smtp | :submission | :submissions | :lmtp, auth: boolean(), require_tls: boolean(), require_auth: boolean(), screen: boolean(), tls_min_version: :"tlsv1.2" | :"tlsv1.3" | nil, tls_ciphers: [String.t()] | nil, proxy_protocol: boolean(), milters: [String.t()], content_filter: String.t() | nil, reinjection: boolean() } ], log: Sovite.Core.Logging.config(), maildir: %{local: String.t() | nil, mailbox: String.t() | nil}, metrics: %{enabled: boolean()}, milter: [ %{ name: String.t(), address: term(), default_action: :tempfail | :accept | :reject, connect_timeout: pos_integer(), command_timeout: pos_integer(), content_timeout: pos_integer() } ], mta_sts: %{ enabled: boolean(), fetch_timeout: pos_integer(), serve: boolean(), address: :inet.ip_address(), port: :inet.port_number(), mode: :enforce | :testing | :none, mx: [String.t()], max_age: pos_integer() }, outbound: %{ enabled: boolean(), max_failure_percent: pos_integer(), min_failures: pos_integer(), window: pos_integer(), suspend_time: pos_integer() }, pipe: %{ required(String.t()) => %{ command: [String.t(), ...], sandbox: [String.t(), ...] | nil, timeout: pos_integer(), directory: Path.t(), env: %{required(String.t()) => String.t()}, trace_headers: boolean() } }, policy: %{timeout: pos_integer(), default_action: String.t()}, queue: %{ directory: Path.t(), max_lifetime: pos_integer(), min_backoff: pos_integer(), max_backoff: pos_integer(), delay_warning: pos_integer() | nil }, rate_limit: %{ client_connections: rate() | nil, client_messages: rate() | nil, client_recipients: rate() | nil, user_messages: rate() | nil, user_recipients: rate() | nil }, raw: map(), restrictions: %{required(atom()) => [String.t()]}, routing: map(), screen: %{ greet_delay: pos_integer() | nil, early_talker_weight: integer(), threshold: pos_integer(), allow_threshold: integer(), lookup_timeout: pos_integer(), cache_time: pos_integer(), dnsbl: [dns_list()], rhsbl: [ %{ zone: String.t(), weight: integer(), codes: list(), check: [:helo | :sender] } ] }, sendmail: %{server: Sovite.Core.Transport.host(), origin: String.t() | nil}, server: %{ hostname: String.t(), authserv_id: String.t(), shutdown_timeout: pos_integer() }, smtp: %{ max_message_size: pos_integer(), max_recipients: pos_integer(), max_connections: pos_integer(), max_connections_per_ip: pos_integer(), max_errors: pos_integer(), command_timeout: pos_integer(), data_timeout: pos_integer(), bare_line_endings: :reject | :normalize, vrfy: boolean(), trusted_networks: [Sovite.Net.network()], max_hops: pos_integer(), requiretls: boolean(), smtputf8: boolean(), chunking: boolean(), binarymime: boolean(), dsn: boolean(), etrn: boolean(), mt_priority: boolean(), deliverby: boolean(), rrvs: boolean(), tarpit_after: pos_integer(), tarpit_delay: pos_integer(), forbid_unauth_pipelining: boolean(), content_filter: String.t() | nil, xclient_networks: [Sovite.Net.network()], xforward_networks: [Sovite.Net.network()], proxy_networks: [Sovite.Net.network()] | nil, proxy_timeout: pos_integer() }, spf: %{ verify: boolean(), helo: boolean(), reject_fail: boolean(), timeout: pos_integer() }, srs: %{ enabled: boolean(), domain: String.t(), secrets: [String.t()], max_age: pos_integer() }, submission: %{ strip_headers: [String.t()], future_release: boolean(), max_future_release: pos_integer() }, tenant: [tenant()], tls: %{ certificate: [%{cert_file: Path.t(), key_file: Path.t()}], min_version: :"tlsv1.2" | :"tlsv1.3", ciphers: [String.t()] | nil, reload_interval: pos_integer(), acme: map() }, tls_rpt: %{ reports: boolean(), report_interval: pos_integer(), report_org: String.t(), report_from: String.t(), contact_info: String.t() }, tracing: %{ enabled: boolean(), endpoint: String.t(), headers: %{optional(String.t()) => String.t()}, sample_ratio: float(), service_name: String.t() } }
@type tenant() :: %{ name: String.t(), domains: [String.t()], users: [String.t()], ip_pool: String.t() | nil, messages: rate() | nil, recipients: rate() | nil, max_message_size: pos_integer() | nil, max_recipients: pos_integer() | nil }
A [[tenant]]: see Sovite.Core.Tenants.
@type tls_level() :: :none | :may | :encrypt | :verify | :dane
Functions
Returns whether any listener offers AUTH.
The SASL mechanisms to offer: auth.mechanisms, or by default those
the backend supports.
@spec default_path() :: Path.t()
Returns the config file path: $SOVITE_CONFIG if set, otherwise
/etc/sovite/sovite.toml.
@spec erase(term()) :: :ok
Forgets the configuration of a server instance that stopped.
@spec get() :: t()
Returns the running configuration. Raises if none has been stored.
Returns the running configuration of a server instance, or nil.
@spec load(Path.t()) :: {:ok, t()} | {:error, [Sovite.Core.Config.Error.t()]}
Reads, parses, and validates the config file at path.
When it sets cluster.shared_config, the settings of that file (the
cluster's shared configuration, see Sovite.Core.Cluster.SharedConfig)
come first, and those of path override them: tables are merged key
by key, other values replaced. A shared file that does not exist yet
is skipped.
@spec parse(String.t()) :: {:ok, t()} | {:error, [Sovite.Core.Config.Error.t()]}
Parses and validates TOML config contents.
@spec put(t()) :: :ok
Stores config as the running configuration.
Stores config as the running configuration of one server instance
(Sovite.Core.Supervisor), and as the running configuration. Parts
that read it on each use, such as the web server, see reloads at once.
@spec resolver(t() | map()) :: Sovite.DNS.resolver()
The DNS resolver for config: Sovite.DNS.InetRes with the [dns]
nameservers, or the system's.
Returns whether TLS certificates are configured (files or ACME).
@spec validate(map()) :: {:ok, t()} | {:error, [Sovite.Core.Config.Error.t()]}
Validates a decoded config map (string keys, as produced by a TOML decoder), fills in defaults, and returns the config struct.