مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-11-30 21:32:30 +00:00
feat: new configuration system (and schema) (#2819)
هذا الالتزام موجود في:
@@ -7,8 +7,8 @@
|
||||
|
||||
class MigrationWaiter
|
||||
|
||||
ATTEMPTS = ENV.fetch("MIGRATION_WAITER_ATTEMPTS", 120)
|
||||
SLEEP_TIME = ENV.fetch("MIGRATION_WAITER_SLEEP_TIME", 2)
|
||||
ATTEMPTS = Postal::Config.migration_waiter.attempts
|
||||
SLEEP_TIME = Postal::Config.migration_waiter.sleep_time
|
||||
|
||||
class << self
|
||||
|
||||
@@ -35,7 +35,7 @@ class MigrationWaiter
|
||||
|
||||
def wait_if_appropriate
|
||||
# Don't wait if not configured
|
||||
return unless ENV.fetch("MIGRATION_WAITER_ENABLED", "false") == "true"
|
||||
return unless Postal::Config.migration_waiter.enabled?
|
||||
|
||||
# Don't wait in the console, rake tasks or rails commands
|
||||
return if console? || rake_task? || rails_command?
|
||||
|
||||
@@ -6,195 +6,128 @@ require "pathname"
|
||||
require "cgi"
|
||||
require "openssl"
|
||||
require "fileutils"
|
||||
require "konfig"
|
||||
require "konfig/sources/environment"
|
||||
require "konfig/sources/yaml"
|
||||
require "dotenv"
|
||||
require "klogger"
|
||||
|
||||
require_relative "error"
|
||||
require_relative "version"
|
||||
require_relative "config_schema"
|
||||
require_relative "legacy_config_source"
|
||||
|
||||
module Postal
|
||||
|
||||
# rubocop:disable Lint/EmptyClass
|
||||
class Config
|
||||
end
|
||||
# rubocop:enable Lint/EmptyClass
|
||||
class << self
|
||||
|
||||
def self.host
|
||||
@host ||= config.web.host || "localhost:5000"
|
||||
end
|
||||
|
||||
def self.protocol
|
||||
@protocol ||= config.web.protocol || "http"
|
||||
end
|
||||
|
||||
def self.host_with_protocol
|
||||
@host_with_protocol ||= "#{protocol}://#{host}"
|
||||
end
|
||||
|
||||
def self.app_root
|
||||
@app_root ||= Pathname.new(File.expand_path("../..", __dir__))
|
||||
end
|
||||
|
||||
def self.config
|
||||
@config ||= begin
|
||||
require "hashie/mash"
|
||||
config = Hashie::Mash.new(defaults)
|
||||
config = config.deep_merge(yaml_config)
|
||||
config.deep_merge(local_yaml_config)
|
||||
end
|
||||
end
|
||||
|
||||
def self.config_root
|
||||
if ENV["POSTAL_CONFIG_ROOT"]
|
||||
@config_root ||= Pathname.new(ENV["POSTAL_CONFIG_ROOT"])
|
||||
else
|
||||
@config_root ||= Pathname.new(File.expand_path("../../config/postal", __dir__))
|
||||
end
|
||||
end
|
||||
|
||||
def self.config_file_path
|
||||
if env == "default"
|
||||
@config_file_path ||= File.join(config_root, "postal.yml")
|
||||
else
|
||||
@config_file_path ||= File.join(config_root, "postal.#{env}.yml")
|
||||
end
|
||||
end
|
||||
|
||||
def self.env
|
||||
@env ||= ENV.fetch("POSTAL_ENV", "default")
|
||||
end
|
||||
|
||||
def self.yaml_config
|
||||
@yaml_config ||= File.exist?(config_file_path) ? YAML.load_file(config_file_path) : {}
|
||||
end
|
||||
|
||||
def self.local_config_file_path
|
||||
@local_config_file_path ||= File.join(config_root, "postal.local.yml")
|
||||
end
|
||||
|
||||
def self.local_yaml_config
|
||||
@local_yaml_config ||= File.exist?(local_config_file_path) ? YAML.load_file(local_config_file_path) : {}
|
||||
end
|
||||
|
||||
def self.defaults_file_path
|
||||
@defaults_file_path ||= app_root.join("config", "postal.defaults.yml")
|
||||
end
|
||||
|
||||
def self.defaults
|
||||
@defaults ||= begin
|
||||
file = File.read(defaults_file_path)
|
||||
yaml = ERB.new(file).result
|
||||
YAML.safe_load(yaml)
|
||||
end
|
||||
end
|
||||
|
||||
# Return a generic logger for use generally throughout Postal.
|
||||
#
|
||||
# @return [Klogger::Logger] A logger instance
|
||||
def self.logger
|
||||
@logger ||= begin
|
||||
k = Klogger.new(nil, destination: Rails.env.test? ? "/dev/null" : $stdout, highlight: Rails.env.development?)
|
||||
k.add_destination(graylog_logging_destination) if config.logging&.graylog&.host.present?
|
||||
k
|
||||
end
|
||||
end
|
||||
|
||||
def self.process_name
|
||||
@process_name ||= begin
|
||||
string = "host:#{Socket.gethostname} pid:#{Process.pid}"
|
||||
string += " procname:#{ENV['PROC_NAME']}" if ENV["PROC_NAME"]
|
||||
string
|
||||
rescue StandardError
|
||||
"pid:#{Process.pid}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.locker_name
|
||||
string = process_name.dup
|
||||
string += " job:#{Thread.current[:job_id]}" if Thread.current[:job_id]
|
||||
string += " thread:#{Thread.current.native_thread_id}"
|
||||
string
|
||||
end
|
||||
|
||||
def self.locker_name_with_suffix(suffix)
|
||||
"#{locker_name} #{suffix}"
|
||||
end
|
||||
|
||||
def self.smtp_from_name
|
||||
config.smtp&.from_name || "Postal"
|
||||
end
|
||||
|
||||
def self.smtp_from_address
|
||||
config.smtp&.from_address || "postal@example.com"
|
||||
end
|
||||
|
||||
def self.smtp_private_key_path
|
||||
config.smtp_server.tls_private_key_path || config_root.join("smtp.key")
|
||||
end
|
||||
|
||||
def self.smtp_private_key
|
||||
@smtp_private_key ||= OpenSSL::PKey.read(File.read(smtp_private_key_path))
|
||||
end
|
||||
|
||||
def self.smtp_certificate_path
|
||||
config.smtp_server.tls_certificate_path || config_root.join("smtp.cert")
|
||||
end
|
||||
|
||||
def self.smtp_certificate_data
|
||||
@smtp_certificate_data ||= File.read(smtp_certificate_path)
|
||||
end
|
||||
|
||||
def self.smtp_certificates
|
||||
@smtp_certificates ||= begin
|
||||
certs = smtp_certificate_data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m)
|
||||
certs.map do |c|
|
||||
OpenSSL::X509::Certificate.new(c)
|
||||
end.freeze
|
||||
end
|
||||
end
|
||||
|
||||
def self.signing_key_path
|
||||
ENV.fetch("POSTAL_SIGNING_KEY_PATH") { config_root.join("signing.key") }
|
||||
end
|
||||
|
||||
def self.signing_key
|
||||
@signing_key ||= OpenSSL::PKey::RSA.new(File.read(signing_key_path))
|
||||
end
|
||||
|
||||
def self.rp_dkim_dns_record
|
||||
public_key = signing_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
|
||||
"v=DKIM1; t=s; h=sha256; p=#{public_key};"
|
||||
end
|
||||
|
||||
class ConfigError < Postal::Error
|
||||
end
|
||||
|
||||
def self.check_config!
|
||||
return if ENV["POSTAL_SKIP_CONFIG_CHECK"].to_i == 1
|
||||
|
||||
unless File.exist?(config_file_path)
|
||||
raise ConfigError, "No config found at #{config_file_path}"
|
||||
# Return the path to the config file
|
||||
#
|
||||
# @return [String]
|
||||
def config_file_path
|
||||
ENV.fetch("POSTAL_CONFIG_FILE_PATH", "config/postal/postal.yml")
|
||||
end
|
||||
|
||||
return if File.exist?(signing_key_path)
|
||||
def initialize_config
|
||||
sources = []
|
||||
|
||||
raise ConfigError, "No signing key found at #{signing_key_path}"
|
||||
end
|
||||
# Load environment variables to begin with. Any config provided
|
||||
# by an environment variable will override any provided in the
|
||||
# config file.
|
||||
Dotenv.load(".env")
|
||||
sources << Konfig::Sources::Environment.new(ENV)
|
||||
|
||||
def self.ip_pools?
|
||||
config.general.use_ip_pools?
|
||||
end
|
||||
# If a config file exists, we need to load that. Config files can
|
||||
# either be legacy (v1) or new (v2). Any file without a 'version'
|
||||
# key is a legacy file whereas new-style config files will include
|
||||
# the 'version: 2' key/value.
|
||||
if File.file?(config_file_path)
|
||||
puts "Loading config from #{config_file_path}"
|
||||
|
||||
def self.graylog_logging_destination
|
||||
@graylog_logging_destination ||= begin
|
||||
notifier = GELF::Notifier.new(config.logging.graylog.host, config.logging.graylog.port, "WAN")
|
||||
proc do |_logger, payload, group_ids|
|
||||
short_message = payload.delete(:message) || "[message missing]"
|
||||
notifier.notify!(short_message: short_message, **{
|
||||
facility: config.logging.graylog.facility,
|
||||
_environment: Rails.env.to_s,
|
||||
_version: Postal::VERSION.to_s,
|
||||
_group_ids: group_ids.join(" ")
|
||||
}.merge(payload.transform_keys { |k| "_#{k}".to_sym }.transform_values(&:to_s)))
|
||||
config_file = File.read(config_file_path)
|
||||
yaml = YAML.safe_load(config_file)
|
||||
config_version = yaml["version"] || 1
|
||||
case config_version
|
||||
when 1
|
||||
puts "WARNING: Using legacy config file format. Upgrade your postal.yml to use"
|
||||
puts "version 2 of the Postal configuration or configure using environment"
|
||||
puts "variables. See https://postalserver.io/config-v2 for details."
|
||||
sources << LegacyConfigSource.new(yaml)
|
||||
when 2
|
||||
sources << Konfig::Sources::YAML.new(config_file)
|
||||
else
|
||||
raise "Invalid version specified in Postal config file. Must be 1 or 2."
|
||||
end
|
||||
else
|
||||
puts "No configuration file found at #{config_file_path}"
|
||||
puts "Only using environment variables for configuration"
|
||||
end
|
||||
|
||||
# Build configuration with the provided sources.
|
||||
Konfig::Config.build(ConfigSchema, sources: sources)
|
||||
end
|
||||
|
||||
def host_with_protocol
|
||||
@host_with_protocol ||= "#{Config.postal.web_protocol}://#{Config.postal.web_hostname}"
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= begin
|
||||
k = Klogger.new(nil, destination: Config.logging.enabled? ? $stdout : "/dev/null", highlight: Config.logging.highlighting_enabled?)
|
||||
k.add_destination(graylog_logging_destination) if Config.gelf.host.present?
|
||||
k
|
||||
end
|
||||
end
|
||||
|
||||
def process_name
|
||||
@process_name ||= begin
|
||||
"host:#{Socket.gethostname} pid:#{Process.pid}"
|
||||
rescue StandardError
|
||||
"pid:#{Process.pid}"
|
||||
end
|
||||
end
|
||||
|
||||
def locker_name
|
||||
string = process_name.dup
|
||||
string += " job:#{Thread.current[:job_id]}" if Thread.current[:job_id]
|
||||
string += " thread:#{Thread.current.native_thread_id}"
|
||||
string
|
||||
end
|
||||
|
||||
def locker_name_with_suffix(suffix)
|
||||
"#{locker_name} #{suffix}"
|
||||
end
|
||||
|
||||
def signing_key
|
||||
@signing_key ||= OpenSSL::PKey::RSA.new(File.read(Config.postal.signing_key_path))
|
||||
end
|
||||
|
||||
def rp_dkim_dns_record
|
||||
public_key = signing_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
|
||||
"v=DKIM1; t=s; h=sha256; p=#{public_key};"
|
||||
end
|
||||
|
||||
def ip_pools?
|
||||
Config.postal.use_ip_pools?
|
||||
end
|
||||
|
||||
def graylog_logging_destination
|
||||
@graylog_logging_destination ||= begin
|
||||
notifier = GELF::Notifier.new(Config.gelf.host, Config.gelf.port, "WAN")
|
||||
proc do |_logger, payload, group_ids|
|
||||
short_message = payload.delete(:message) || "[message missing]"
|
||||
notifier.notify!(short_message: short_message, **{
|
||||
facility: Config.gelf.facility,
|
||||
_environment: Config.rails.environment,
|
||||
_version: Postal::VERSION.to_s,
|
||||
_group_ids: group_ids.join(" ")
|
||||
}.merge(payload.transform_keys { |k| "_#{k}".to_sym }.transform_values(&:to_s)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Config = initialize_config
|
||||
|
||||
end
|
||||
|
||||
464
lib/postal/config_schema.rb
Normal file
464
lib/postal/config_schema.rb
Normal file
@@ -0,0 +1,464 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "uri"
|
||||
|
||||
module Postal
|
||||
|
||||
# REMEMBER: If you change the schema, remember to regenerate the configuration docs
|
||||
# using the rake command below:
|
||||
#
|
||||
# rake postal:generate_config_docs
|
||||
|
||||
ConfigSchema = Konfig::Schema.draw do
|
||||
group :postal do
|
||||
string :web_hostname do
|
||||
description "The hostname that the Postal web interface runs on"
|
||||
default "postal.example.com"
|
||||
end
|
||||
|
||||
string :web_protocol do
|
||||
description "The HTTP protocol to use for the Postal web interface"
|
||||
default "https"
|
||||
end
|
||||
|
||||
string :smtp_hostname do
|
||||
description "The hostname that the Postal SMTP server runs on"
|
||||
default "postal.example.com"
|
||||
end
|
||||
|
||||
boolean :use_ip_pools do
|
||||
description "Should IP pools be enabled for this installation?"
|
||||
default false
|
||||
end
|
||||
|
||||
integer :default_maximum_delivery_attempts do
|
||||
description "The maximum number of delivery attempts"
|
||||
default 18
|
||||
end
|
||||
|
||||
integer :default_maximum_hold_expiry_days do
|
||||
description "The number of days to hold a message before they will be expired"
|
||||
default 7
|
||||
end
|
||||
|
||||
integer :default_suppression_list_automatic_removal_days do
|
||||
description "The number of days an address will remain in a suppression list before being removed"
|
||||
default 30
|
||||
end
|
||||
|
||||
integer :default_spam_threshold do
|
||||
description "The default threshold at which a message should be treated as spam"
|
||||
default 5
|
||||
end
|
||||
|
||||
integer :default_spam_failure_threshold do
|
||||
description "The default threshold at which a message should be treated as spam failure"
|
||||
default 20
|
||||
end
|
||||
|
||||
boolean :use_local_ns_for_domain_verification do
|
||||
description "Domain verification and checking usually checks with a domain's nameserver. Enable this to check with the server's local nameservers."
|
||||
default false
|
||||
end
|
||||
|
||||
boolean :use_resent_sender_header do
|
||||
description "Append a Resend-Sender header to all outgoing e-mails"
|
||||
default true
|
||||
end
|
||||
|
||||
string :signing_key_path do
|
||||
description "Path to the private key used for signing"
|
||||
default "config/postal/signing.key"
|
||||
end
|
||||
|
||||
string :smtp_relays do
|
||||
array
|
||||
description "An array of SMTP relays in the format of smtp://host:port"
|
||||
transform do |value|
|
||||
uri = URI.parse(value)
|
||||
query = uri.query ? CGI.parse(uri.query) : {}
|
||||
{
|
||||
host: uri.host,
|
||||
port: uri.port || 25,
|
||||
ssl_mode: query["ssl_mode"]&.first || "Auto"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
group :web_server do
|
||||
integer :default_port do
|
||||
description "The default port the web server should listen on unless overriden by the PORT environment variable"
|
||||
default 5000
|
||||
end
|
||||
|
||||
string :default_bind_address do
|
||||
description "The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :max_threads do
|
||||
description "The maximum number of threads which can be used by the web server"
|
||||
default 5
|
||||
end
|
||||
end
|
||||
|
||||
group :worker do
|
||||
integer :default_health_server_port do
|
||||
description "The default port for the worker health server to listen on"
|
||||
default 9090
|
||||
end
|
||||
|
||||
string :default_health_server_bind_address do
|
||||
description "The default bind address for the worker health server to listen on"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
end
|
||||
|
||||
group :main_db do
|
||||
string :host do
|
||||
description "Hostname for the main MariaDB server"
|
||||
default "localhost"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The MariaDB port to connect to"
|
||||
default 3306
|
||||
end
|
||||
|
||||
string :username do
|
||||
description "The MariaDB username"
|
||||
default "postal"
|
||||
end
|
||||
|
||||
string :password do
|
||||
description "The MariaDB password"
|
||||
end
|
||||
|
||||
string :database do
|
||||
description "The MariaDB database name"
|
||||
default "postal"
|
||||
end
|
||||
|
||||
integer :pool_size do
|
||||
description "The maximum size of the MariaDB connection pool"
|
||||
default 5
|
||||
end
|
||||
|
||||
string :encoding do
|
||||
description "The encoding to use when connecting to the MariaDB database"
|
||||
default "utf8mb4"
|
||||
end
|
||||
end
|
||||
|
||||
group :message_db do
|
||||
string :host do
|
||||
description "Hostname for the MariaDB server which stores the mail server databases"
|
||||
default "localhost"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The MariaDB port to connect to"
|
||||
default 3306
|
||||
end
|
||||
|
||||
string :username do
|
||||
description "The MariaDB username"
|
||||
default "postal"
|
||||
end
|
||||
|
||||
string :password do
|
||||
description "The MariaDB password"
|
||||
end
|
||||
|
||||
string :database_name_prefix do
|
||||
description "The MariaDB prefix to add to database names"
|
||||
default "postal"
|
||||
end
|
||||
end
|
||||
|
||||
group :logging do
|
||||
boolean :rails_log_enabled do
|
||||
description "Enable the default Rails logger"
|
||||
default false
|
||||
end
|
||||
|
||||
string :sentry_dsn do
|
||||
description "A DSN which should be used to report exceptions to Sentry"
|
||||
end
|
||||
|
||||
boolean :enabled do
|
||||
description "Enable the Postal logger to log to STDOUT"
|
||||
default true
|
||||
end
|
||||
|
||||
boolean :highlighting_enabled do
|
||||
description "Enable highlighting of log lines"
|
||||
default false
|
||||
end
|
||||
end
|
||||
|
||||
group :gelf do
|
||||
string :host do
|
||||
description "GELF-capable host to send logs to"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "GELF port to send logs to"
|
||||
default 12_201
|
||||
end
|
||||
|
||||
string :facility do
|
||||
description "The facility name to add to all log entries sent to GELF"
|
||||
default "postal"
|
||||
end
|
||||
end
|
||||
|
||||
group :smtp_server do
|
||||
integer :default_port do
|
||||
description "The default port the SMTP server should listen on unless overriden by the PORT environment variable"
|
||||
default 25
|
||||
end
|
||||
|
||||
string :default_bind_address do
|
||||
description "The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :default_health_server_port do
|
||||
description "The default port for the SMTP server health server to listen on"
|
||||
default 9091
|
||||
end
|
||||
|
||||
string :default_health_server_bind_address do
|
||||
description "The default bind address for the SMTP server health server to listen on"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
boolean :tls_enabled do
|
||||
description "Enable TLS for the SMTP server (requires certificate)"
|
||||
default false
|
||||
end
|
||||
|
||||
string :tls_certificate_path do
|
||||
description "The path to the SMTP server's TLS certificate"
|
||||
default "config/postal/smtp.cert"
|
||||
end
|
||||
|
||||
string :tls_private_key_path do
|
||||
description "The path to the SMTP server's TLS private key"
|
||||
default "config/postal/smtp.key"
|
||||
end
|
||||
|
||||
string :tls_ciphers do
|
||||
description "Override ciphers to use for SSL"
|
||||
end
|
||||
|
||||
string :ssl_version do
|
||||
description "The SSL versions which are supported"
|
||||
default "SSLv23"
|
||||
end
|
||||
|
||||
boolean :proxy_protocol do
|
||||
description "Enable proxy protocol for use behind some load balancers"
|
||||
default false
|
||||
end
|
||||
|
||||
boolean :log_connections do
|
||||
description "Enable connection logging"
|
||||
default false
|
||||
end
|
||||
|
||||
integer :max_message_size do
|
||||
description "The maximum message size to accept from the SMTP server (in MB)"
|
||||
default 14
|
||||
end
|
||||
|
||||
string :log_ip_address_exclusion_matcher do
|
||||
description "A regular expression to use to exclude connections from logging"
|
||||
end
|
||||
end
|
||||
|
||||
group :dns do
|
||||
string :mx_records do
|
||||
description "The names of the default MX records"
|
||||
array
|
||||
default ["mx1.postal.example.com", "mx2.postal.example.com"]
|
||||
end
|
||||
|
||||
string :spf_include do
|
||||
description "The location of the SPF record"
|
||||
default "spf.postal.example.com"
|
||||
end
|
||||
|
||||
string :return_path_domain do
|
||||
description "The return path hostname"
|
||||
default "rp.postal.example.com"
|
||||
end
|
||||
|
||||
string :route_domain do
|
||||
description "The domain to use for hosting route-specific addresses"
|
||||
default "routes.postal.example.com"
|
||||
end
|
||||
|
||||
string :track_domain do
|
||||
description "The CNAME which tracking domains should be pointed to"
|
||||
default "track.postal.example.com"
|
||||
end
|
||||
|
||||
string :helo_hostname do
|
||||
description "The hostname to use in HELO/EHLO when connecting to external SMTP servers"
|
||||
end
|
||||
|
||||
string :dkim_identifier do
|
||||
description "The identifier to use for DKIM keys in DNS records"
|
||||
default "postal"
|
||||
end
|
||||
|
||||
string :domain_verify_prefix do
|
||||
description "The prefix to add before TXT record verification string"
|
||||
default "postal-verification"
|
||||
end
|
||||
|
||||
string :custom_return_path_prefix do
|
||||
description "The domain to use on external domains which points to the Postal return path domain"
|
||||
default "psrp"
|
||||
end
|
||||
end
|
||||
|
||||
group :smtp do
|
||||
string :host do
|
||||
description "The hostname to send application-level e-mails to"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The port number to send application-level e-mails to"
|
||||
default 25
|
||||
end
|
||||
|
||||
string :username do
|
||||
description "The username to use when authentication to the SMTP server"
|
||||
end
|
||||
|
||||
string :password do
|
||||
description "The password to use when authentication to the SMTP server"
|
||||
end
|
||||
|
||||
string :from_name do
|
||||
description "The name to use as the from name outgoing emails from Postal"
|
||||
default "Postal"
|
||||
end
|
||||
|
||||
string :from_address do
|
||||
description "The e-mail to use as the from address outgoing emails from Postal"
|
||||
default "postal@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
group :rails do
|
||||
string :environment do
|
||||
description "The Rails environment to run the application in"
|
||||
default "production"
|
||||
end
|
||||
|
||||
string :secret_key do
|
||||
description "The secret key used to sign and encrypt cookies and session data in the application"
|
||||
end
|
||||
end
|
||||
|
||||
group :rspamd do
|
||||
boolean :enabled do
|
||||
description "Enable rspamd for message inspection"
|
||||
default false
|
||||
end
|
||||
|
||||
string :host do
|
||||
description "The hostname of the rspamd server"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The port of the rspamd server"
|
||||
default 11_334
|
||||
end
|
||||
|
||||
boolean :ssl do
|
||||
description "Enable SSL for the rspamd connection"
|
||||
default false
|
||||
end
|
||||
|
||||
string :password do
|
||||
description "The password for the rspamd server"
|
||||
end
|
||||
|
||||
string :flags do
|
||||
description "Any flags for the rspamd server"
|
||||
end
|
||||
end
|
||||
|
||||
group :spamd do
|
||||
boolean :enabled do
|
||||
description "Enable SpamAssassin for message inspection"
|
||||
default false
|
||||
end
|
||||
|
||||
string :host do
|
||||
description "The hostname for the SpamAssassin server"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The port of the SpamAssassin server"
|
||||
default 783
|
||||
end
|
||||
end
|
||||
|
||||
group :clamav do
|
||||
boolean :enabled do
|
||||
description "Enable ClamAV for message inspection"
|
||||
default false
|
||||
end
|
||||
|
||||
string :host do
|
||||
description "The host of the ClamAV server"
|
||||
default "127.0.0.1"
|
||||
end
|
||||
|
||||
integer :port do
|
||||
description "The port of the ClamAV server"
|
||||
default 2000
|
||||
end
|
||||
end
|
||||
|
||||
group :smtp_client do
|
||||
integer :open_timeout do
|
||||
description "The open timeout for outgoing SMTP connections"
|
||||
default 30
|
||||
end
|
||||
|
||||
integer :read_timeout do
|
||||
description "The read timeout for outgoing SMTP connections"
|
||||
default 30
|
||||
end
|
||||
end
|
||||
|
||||
group :migration_waiter do
|
||||
boolean :enabled do
|
||||
description "Wait for all migrations to run before starting a process"
|
||||
default false
|
||||
end
|
||||
|
||||
integer :attempts do
|
||||
description "The number of attempts to try waiting for migrations to complete before start"
|
||||
default 120
|
||||
end
|
||||
|
||||
integer :sleep_time do
|
||||
description "The number of seconds to wait between each migration check"
|
||||
default 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
122
lib/postal/legacy_config_source.rb
Normal file
122
lib/postal/legacy_config_source.rb
Normal file
@@ -0,0 +1,122 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "konfig/sources/abstract"
|
||||
require "konfig/error"
|
||||
|
||||
module Postal
|
||||
class LegacyConfigSource < Konfig::Sources::Abstract
|
||||
|
||||
# This maps all the new configuration values to where they
|
||||
# exist in the old YAML file. The source will load any YAML
|
||||
# file that has been provided to this source in order. A
|
||||
# warning will be generated to the console for configuration
|
||||
# loaded from this format.
|
||||
MAPPING = {
|
||||
"postal.web_hostname" => -> (c) { c.dig("web", "host") },
|
||||
"postal.web_protocol" => -> (c) { c.dig("web", "protocol") },
|
||||
"postal.smtp_hostname" => -> (c) { c.dig("dns", "smtp_server_hostname") },
|
||||
"postal.use_ip_pools" => -> (c) { c.dig("general", "use_ip_pools") },
|
||||
"logging.sentry_dsn" => -> (c) { c.dig("general", "exception_url") },
|
||||
"postal.default_maximum_delivery_attempts" => -> (c) { c.dig("general", "maximum_delivery_attempts") },
|
||||
"postal.default_maximum_hold_expiry_days" => -> (c) { c.dig("general", "maximum_hold_expiry_days") },
|
||||
"postal.default_suppression_list_automatic_removal_days" => -> (c) { c.dig("general", "suppression_list_removal_delay") },
|
||||
"postal.use_local_ns_for_domain_verification" => -> (c) { c.dig("general", "use_local_ns_for_domains") },
|
||||
"postal.default_spam_threshold" => -> (c) { c.dig("general", "default_spam_threshold") },
|
||||
"postal.default_spam_failure_threshold" => -> (c) { c.dig("general", "default_spam_failure_threshold") },
|
||||
"postal.use_resent_sender_header" => -> (c) { c.dig("general", "use_resent_sender_header") },
|
||||
# SMTP relays must be converted to the new URI style format and they'll
|
||||
# then be transformed back to a hash by the schema transform.
|
||||
"postal.smtp_relays" => -> (c) { c["smtp_relays"]&.map { |r| "smtp://#{r['hostname']}:#{r['port']}?ssl_mode=#{r['ssl_mode']}" } },
|
||||
|
||||
"web_server.default_bind_address" => -> (c) { c.dig("web_server", "bind_address") },
|
||||
"web_server.default_port" => -> (c) { c.dig("web_server", "port") },
|
||||
"web_server.max_threads" => -> (c) { c.dig("web_server", "max_threads") },
|
||||
|
||||
"main_db.host" => -> (c) { c.dig("main_db", "host") },
|
||||
"main_db.port" => -> (c) { c.dig("main_db", "port") },
|
||||
"main_db.username" => -> (c) { c.dig("main_db", "username") },
|
||||
"main_db.password" => -> (c) { c.dig("main_db", "password") },
|
||||
"main_db.database" => -> (c) { c.dig("main_db", "database") },
|
||||
"main_db.pool_size" => -> (c) { c.dig("main_db", "pool_size") },
|
||||
"main_db.encoding" => -> (c) { c.dig("main_db", "encoding") },
|
||||
|
||||
"message_db.host" => -> (c) { c.dig("message_db", "host") },
|
||||
"message_db.port" => -> (c) { c.dig("message_db", "port") },
|
||||
"message_db.username" => -> (c) { c.dig("message_db", "username") },
|
||||
"message_db.password" => -> (c) { c.dig("message_db", "password") },
|
||||
"message_db.database_name_prefix" => -> (c) { c.dig("message_db", "prefix") },
|
||||
|
||||
"logging.rails_log_enabled" => -> (c) { c.dig("logging", "rails_log") },
|
||||
|
||||
"gelf.host" => -> (c) { c.dig("logging", "graylog", "host") },
|
||||
"gelf.port" => -> (c) { c.dig("logging", "graylog", "port") },
|
||||
"gelf.facility" => -> (c) { c.dig("logging", "graylog", "facility") },
|
||||
|
||||
"smtp_server.default_port" => -> (c) { c.dig("smtp_server", "port") },
|
||||
"smtp_server.default_bind_address" => -> (c) { c.dig("smtp_server", "bind_address") },
|
||||
"smtp_server.tls_enabled" => -> (c) { c.dig("smtp_server", "tls_enabled") },
|
||||
"smtp_server.tls_certificate_path" => -> (c) { c.dig("smtp_server", "tls_certificate_path") },
|
||||
"smtp_server.tls_private_key_path" => -> (c) { c.dig("smtp_server", "tls_private_key_path") },
|
||||
"smtp_server.tls_ciphers" => -> (c) { c.dig("smtp_server", "tls_ciphers") },
|
||||
"smtp_server.ssl_version" => -> (c) { c.dig("smtp_server", "ssl_version") },
|
||||
"smtp_server.proxy_protocol" => -> (c) { c.dig("smtp_server", "proxy_protocol") },
|
||||
"smtp_server.log_connections" => -> (c) { c.dig("smtp_server", "log_connect") },
|
||||
"smtp_server.max_message_size" => -> (c) { c.dig("smtp_server", "max_message_size") },
|
||||
|
||||
"dns.mx_records" => -> (c) { c.dig("dns", "mx_records") },
|
||||
"dns.spf_include" => -> (c) { c.dig("dns", "spf_include") },
|
||||
"dns.return_path_domain" => -> (c) { c.dig("dns", "return_path") },
|
||||
"dns.route_domain" => -> (c) { c.dig("dns", "route_domain") },
|
||||
"dns.track_domain" => -> (c) { c.dig("dns", "track_domain") },
|
||||
"dns.helo_hostname" => -> (c) { c.dig("dns", "helo_hostname") },
|
||||
"dns.dkim_identifier" => -> (c) { c.dig("dns", "dkim_identifier") },
|
||||
"dns.domain_verify_prefix" => -> (c) { c.dig("dns", "domain_verify_prefix") },
|
||||
"dns.custom_return_path_prefix" => -> (c) { c.dig("dns", "custom_return_path_prefix") },
|
||||
|
||||
"smtp.host" => -> (c) { c.dig("smtp", "host") },
|
||||
"smtp.port" => -> (c) { c.dig("smtp", "port") },
|
||||
"smtp.username" => -> (c) { c.dig("smtp", "username") },
|
||||
"smtp.password" => -> (c) { c.dig("smtp", "password") },
|
||||
"smtp.from_name" => -> (c) { c.dig("smtp", "from_name") },
|
||||
"smtp.from_address" => -> (c) { c.dig("smtp", "from_address") },
|
||||
|
||||
"rails.environment" => -> (c) { c.dig("rails", "environment") },
|
||||
"rails.secret_key" => -> (c) { c.dig("rails", "secret_key") },
|
||||
|
||||
"rspamd.enabled" => -> (c) { c.dig("rspamd", "enabled") },
|
||||
"rspamd.host" => -> (c) { c.dig("rspamd", "host") },
|
||||
"rspamd.port" => -> (c) { c.dig("rspamd", "port") },
|
||||
"rspamd.ssl" => -> (c) { c.dig("rspamd", "ssl") },
|
||||
"rspamd.password" => -> (c) { c.dig("rspamd", "password") },
|
||||
"rspamd.flags" => -> (c) { c.dig("rspamd", "flags") },
|
||||
|
||||
"spamd.enabled" => -> (c) { c.dig("spamd", "enabled") },
|
||||
"spamd.host" => -> (c) { c.dig("spamd", "host") },
|
||||
"spamd.port" => -> (c) { c.dig("spamd", "port") },
|
||||
|
||||
"clamav.enabled" => -> (c) { c.dig("clamav", "enabled") },
|
||||
"clamav.host" => -> (c) { c.dig("clamav", "host") },
|
||||
"clamav.port" => -> (c) { c.dig("clamav", "port") },
|
||||
|
||||
"smtp_client.open_timeout" => -> (c) { c.dig("smtp_client", "open_timeout") },
|
||||
"smtp_client.read_timeout" => -> (c) { c.dig("smtp_client", "read_timeout") }
|
||||
|
||||
}.freeze
|
||||
|
||||
def initialize(config)
|
||||
super()
|
||||
@config = config
|
||||
end
|
||||
|
||||
def get(path, attribute: nil)
|
||||
path_string = path.join(".")
|
||||
raise Konfig::ValueNotPresentError unless MAPPING.key?(path_string)
|
||||
|
||||
legacy_value = MAPPING[path_string].call(@config)
|
||||
raise Konfig::ValueNotPresentError if legacy_value.nil?
|
||||
|
||||
legacy_value
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -63,11 +63,11 @@ module Postal
|
||||
|
||||
def establish_connection
|
||||
Mysql2::Client.new(
|
||||
host: Postal.config.message_db.host,
|
||||
username: Postal.config.message_db.username,
|
||||
password: Postal.config.message_db.password,
|
||||
port: Postal.config.message_db.port,
|
||||
encoding: Postal.config.message_db.encoding || "utf8mb4"
|
||||
host: Postal::Config.message_db.host,
|
||||
username: Postal::Config.message_db.username,
|
||||
password: Postal::Config.message_db.password,
|
||||
port: Postal::Config.message_db.port,
|
||||
encoding: Postal::Config.message_db.encoding
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ module Postal
|
||||
# Return the correct database name
|
||||
#
|
||||
def database_name
|
||||
@database_name ||= "#{Postal.config.message_db.prefix}-server-#{@server_id}"
|
||||
@database_name ||= "#{Postal::Config.message_db.database_name_prefix}-server-#{@server_id}"
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -128,7 +128,7 @@ module Postal
|
||||
#
|
||||
def create_delivery(status, options = {})
|
||||
delivery = Delivery.create(self, options.merge(status: status))
|
||||
hold_expiry = status == "Held" ? Postal.config.general.maximum_hold_expiry_days.days.from_now.to_f : nil
|
||||
hold_expiry = status == "Held" ? Postal::Config.postal.default_maximum_hold_expiry_days.days.from_now.to_f : nil
|
||||
update(status: status, last_delivery_attempt: delivery.timestamp.to_f, held: status == "Held", hold_expiry: hold_expiry)
|
||||
delivery
|
||||
end
|
||||
@@ -511,7 +511,7 @@ module Postal
|
||||
# Was thsi message sent to a return path?
|
||||
#
|
||||
def rcpt_to_return_path?
|
||||
!!(rcpt_to =~ /@#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./)
|
||||
!!(rcpt_to =~ /@#{Regexp.escape(Postal::Config.dns.custom_return_path_prefix)}\./)
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -9,7 +9,7 @@ module Postal
|
||||
end
|
||||
|
||||
def add(type, address, options = {})
|
||||
keep_until = (options[:days] || Postal.config.general.suppression_list_removal_delay).days.from_now.to_f
|
||||
keep_until = (options[:days] || Postal::Config.postal.default_suppression_list_automatic_removal_days).days.from_now.to_f
|
||||
if existing = @database.select("suppressions", where: { type: type, address: address }, limit: 1).first
|
||||
reason = options[:reason] || existing["reason"]
|
||||
@database.update("suppressions", { reason: reason, keep_until: keep_until }, where: { id: existing["id"] })
|
||||
|
||||
@@ -24,14 +24,14 @@ module Postal
|
||||
# installation.
|
||||
def inspectors
|
||||
[].tap do |inspectors|
|
||||
if Postal.config.rspamd&.enabled
|
||||
inspectors << MessageInspectors::Rspamd.new(Postal.config.rspamd)
|
||||
elsif Postal.config.spamd&.enabled
|
||||
inspectors << MessageInspectors::SpamAssassin.new(Postal.config.spamd)
|
||||
if Postal::Config.rspamd.enabled?
|
||||
inspectors << MessageInspectors::Rspamd.new(Postal::Config.rspamd)
|
||||
elsif Postal::Config.spamd.enabled?
|
||||
inspectors << MessageInspectors::SpamAssassin.new(Postal::Config.spamd)
|
||||
end
|
||||
|
||||
if Postal.config.clamav&.enabled
|
||||
inspectors << MessageInspectors::Clamav.new(Postal.config.clamav)
|
||||
if Postal::Config.clamav.enabled?
|
||||
inspectors << MessageInspectors::Clamav.new(Postal::Config.clamav)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
37
lib/postal/yaml_config_exporter.rb
Normal file
37
lib/postal/yaml_config_exporter.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "konfig/exporters/abstract"
|
||||
|
||||
module Postal
|
||||
class YamlConfigExporter < Konfig::Exporters::Abstract
|
||||
|
||||
def export
|
||||
contents = []
|
||||
contents << "version: 2"
|
||||
contents << ""
|
||||
|
||||
@schema.groups.each do |group_name, group|
|
||||
contents << "#{group_name}:"
|
||||
group.attributes.each do |name, attr|
|
||||
contents << " # #{attr.description}"
|
||||
if attr.array?
|
||||
if attr.default.blank?
|
||||
contents << " #{name}: []"
|
||||
else
|
||||
contents << " #{name}:"
|
||||
attr.default.each do |d|
|
||||
contents << " - #{d}"
|
||||
end
|
||||
end
|
||||
else
|
||||
contents << " #{name}: #{attr.default}"
|
||||
end
|
||||
end
|
||||
contents << ""
|
||||
end
|
||||
|
||||
contents.join("\n")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,18 @@ namespace :postal do
|
||||
server.message_db.provisioner.migrate
|
||||
end
|
||||
end
|
||||
|
||||
desc "Generate configuration documentation"
|
||||
task generate_config_docs: :environment do
|
||||
require "konfig/exporters/env_vars_as_markdown"
|
||||
|
||||
FileUtils.mkdir_p("doc/config")
|
||||
output = Konfig::Exporters::EnvVarsAsMarkdown.new(Postal::ConfigSchema).export
|
||||
File.write("doc/config/environment-variables.md", output)
|
||||
|
||||
output = Postal::YamlConfigExporter.new(Postal::ConfigSchema).export
|
||||
File.write("doc/config/yaml.yml", output)
|
||||
end
|
||||
end
|
||||
|
||||
Rake::Task["db:migrate"].enhance do
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم