All checks were successful
Build / precommit (pull_request) Successful in 5m1s
- add SMTP submission listener on port 587 with TLS requirement - configure HAProxy frontend/backend for submission with send-proxy-v2 support - add send-proxy-v2 support to all listeners - add dynamic HAProxy node discovery for proxy trusted networks - use service hostname instead of node FQDN for autoconfig/autodiscover - remove redundant IMAP/IMAPS/SMTP alt-names from TLS certificates - update VRRP CNAME configuration to use mail.main.unkin.net
246 lines
8.1 KiB
Puppet
246 lines
8.1 KiB
Puppet
# @summary Main class for managing Stalwart Mail Server
|
|
#
|
|
# This class provides a comprehensive setup of Stalwart Mail Server with
|
|
# clustering, authentication, storage, and protocol support.
|
|
#
|
|
# @example Basic Stalwart setup
|
|
# class { 'stalwart':
|
|
# node_id => 1,
|
|
# postgresql_host => 'pgsql.example.com',
|
|
# postgresql_database => 'stalwart',
|
|
# postgresql_user => 'stalwart',
|
|
# postgresql_password => Sensitive('secretpassword'),
|
|
# s3_endpoint => 'https://ceph-rgw.example.com',
|
|
# s3_bucket => 'stalwart-blobs',
|
|
# s3_access_key => 'accesskey',
|
|
# s3_secret_key => Sensitive('secretkey'),
|
|
# domains => ['example.com'],
|
|
# postfix_relay_host => 'postfix.example.com',
|
|
# }
|
|
#
|
|
# @param node_id
|
|
# Unique identifier for this node in the cluster (1-N). If not specified,
|
|
# automatically calculated based on sorted position in cluster member list.
|
|
#
|
|
# @param cluster_role
|
|
# Role name for cluster member discovery via query_nodes()
|
|
#
|
|
#
|
|
# @param postgresql_host
|
|
# PostgreSQL server hostname/IP
|
|
#
|
|
# @param postgresql_port
|
|
# PostgreSQL server port
|
|
#
|
|
# @param postgresql_database
|
|
# PostgreSQL database name
|
|
#
|
|
# @param postgresql_user
|
|
# PostgreSQL username
|
|
#
|
|
# @param postgresql_password
|
|
# PostgreSQL password (Sensitive)
|
|
#
|
|
# @param postgresql_ssl
|
|
# Enable SSL/TLS for PostgreSQL connections
|
|
#
|
|
# @param s3_endpoint
|
|
# S3/Ceph-RGW endpoint URL
|
|
#
|
|
# @param s3_bucket
|
|
# S3 bucket name for blob storage
|
|
#
|
|
# @param s3_region
|
|
# S3 region
|
|
#
|
|
# @param s3_access_key
|
|
# S3 access key
|
|
#
|
|
# @param s3_secret_key
|
|
# S3 secret key (Sensitive)
|
|
#
|
|
# @param s3_key_prefix
|
|
# S3 key prefix for stalwart objects
|
|
#
|
|
# @param domains
|
|
# Array of domains this server handles
|
|
#
|
|
# @param postfix_relay_host
|
|
# Postfix relay host for SMTP delivery
|
|
#
|
|
# @param bind_address
|
|
# IP address to bind services to
|
|
#
|
|
# @param advertise_address
|
|
# IP address to advertise to cluster members
|
|
#
|
|
# @param enable_imap
|
|
# Enable IMAP protocol listener
|
|
#
|
|
# @param enable_imap_tls
|
|
# Enable IMAP over TLS listener
|
|
#
|
|
# @param enable_http
|
|
# Enable HTTP listener for JMAP/WebDAV/Autodiscovery
|
|
#
|
|
# @param enable_smtp_relay
|
|
# Enable SMTP for postfix relay communication
|
|
#
|
|
# @param enable_smtp_submission
|
|
# Enable SMTP submission listener on port 587
|
|
#
|
|
# @param haproxy_role
|
|
# Role name for HAProxy nodes to include in proxy trusted networks
|
|
#
|
|
# @param service_hostname
|
|
# Service hostname used for autoconfig/autodiscover and SMTP greeting
|
|
#
|
|
# @param package_ensure
|
|
# Package version to install
|
|
#
|
|
# @param config_dir
|
|
# Stalwart configuration directory
|
|
#
|
|
# @param data_dir
|
|
# Stalwart data directory
|
|
#
|
|
# @param log_level
|
|
# Logging verbosity level
|
|
#
|
|
# @param manage_firewall
|
|
# Whether to manage firewall rules
|
|
#
|
|
# @param tls_cert
|
|
# Path to TLS certificate file
|
|
#
|
|
# @param tls_key
|
|
# Path to TLS private key file
|
|
#
|
|
# @param manage_dns_records
|
|
# Whether to create DNS autodiscovery records
|
|
#
|
|
class stalwart (
|
|
String $cluster_role,
|
|
Stdlib::Host $postgresql_host,
|
|
String $postgresql_database,
|
|
String $postgresql_user,
|
|
Sensitive[String] $postgresql_password,
|
|
Stdlib::HTTPUrl $s3_endpoint,
|
|
String $s3_bucket,
|
|
String $s3_access_key,
|
|
Sensitive[String] $s3_secret_key,
|
|
Array[Stdlib::Fqdn] $domains,
|
|
Stdlib::Host $postfix_relay_host,
|
|
Optional[Integer] $node_id = undef,
|
|
Stdlib::Port $postgresql_port = 5432,
|
|
Boolean $postgresql_ssl = true,
|
|
String $s3_region = 'us-east-1',
|
|
String $s3_key_prefix = 'stalwart/',
|
|
Stdlib::IP::Address $bind_address = $facts['networking']['ip'],
|
|
Stdlib::IP::Address $advertise_address = $facts['networking']['ip'],
|
|
Boolean $enable_imap = true,
|
|
Boolean $enable_imap_tls = true,
|
|
Boolean $enable_http = true,
|
|
Boolean $enable_smtp_relay = true,
|
|
Boolean $enable_smtp_submission = true,
|
|
String $haproxy_role = 'roles::infra::halb::haproxy2',
|
|
Stdlib::Fqdn $service_hostname = $facts['networking']['fqdn'],
|
|
String $package_ensure = 'present',
|
|
Stdlib::Absolutepath $config_dir = '/opt/stalwart/etc',
|
|
Stdlib::Absolutepath $data_dir = '/var/lib/stalwart',
|
|
Enum['error','warn','info','debug','trace'] $log_level = 'info',
|
|
Boolean $manage_firewall = false,
|
|
Stdlib::Absolutepath $tls_cert = '/etc/pki/tls/vault/certificate.crt',
|
|
Stdlib::Absolutepath $tls_key = '/etc/pki/tls/vault/private.key',
|
|
Boolean $manage_dns_records = true,
|
|
Optional[Stdlib::Fqdn] $loadbalancer_host = undef,
|
|
String $fallback_admin_user = 'admin',
|
|
Sensitive[String] $fallback_admin_password = Sensitive('admin'),
|
|
Stdlib::Absolutepath $webadmin_unpack_path = "${data_dir}/webadmin",
|
|
Stdlib::HTTPUrl $webadmin_resource_url = 'https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip',
|
|
Boolean $webadmin_auto_update = true,
|
|
) {
|
|
|
|
# Calculate node_id from last 4 digits of hostname if not provided
|
|
$my_fqdn = $facts['networking']['fqdn']
|
|
$hostname = $facts['networking']['hostname']
|
|
|
|
# Query cluster members for validation
|
|
$cluster_query = "enc_role='${cluster_role}' and country='${facts['country']}' and region='${facts['region']}'"
|
|
$cluster_members_raw = query_nodes($cluster_query, 'networking.fqdn')
|
|
$cluster_members = $cluster_members_raw ? {
|
|
undef => [],
|
|
default => $cluster_members_raw,
|
|
}
|
|
$sorted_cluster_members = sort($cluster_members)
|
|
|
|
# Calculate cluster information for templates
|
|
$other_cluster_members = $sorted_cluster_members.filter |$member| { $member != $my_fqdn }
|
|
$cluster_size = length($sorted_cluster_members)
|
|
|
|
# Query HAProxy nodes for proxy trusted networks
|
|
$haproxy_query = "enc_role='${haproxy_role}' and country='${facts['country']}' and region='${facts['region']}'"
|
|
$haproxy_members_raw = query_nodes($haproxy_query, 'networking.ip')
|
|
$haproxy_ips = $haproxy_members_raw ? {
|
|
undef => [],
|
|
default => sort($haproxy_members_raw),
|
|
}
|
|
|
|
# Extract last 4 digits from hostname (e.g., ausyd1nxvm1234 -> 1234)
|
|
if $hostname =~ /^.*(\d{4})$/ {
|
|
$hostname_digits = $1
|
|
$calculated_node_id = Integer($hostname_digits)
|
|
} else {
|
|
fail("Unable to extract 4-digit node ID from hostname '${hostname}'. Hostname must end with 4 digits or specify node_id manually.")
|
|
}
|
|
|
|
# Use provided node_id or calculated one
|
|
$effective_node_id = $node_id ? {
|
|
undef => $calculated_node_id,
|
|
default => $node_id,
|
|
}
|
|
|
|
# Validate parameters
|
|
if $effective_node_id < 1 {
|
|
fail('node_id must be a positive integer')
|
|
}
|
|
|
|
if empty($domains) {
|
|
fail('At least one domain must be specified')
|
|
}
|
|
|
|
if !($my_fqdn in $sorted_cluster_members) {
|
|
fail("This node (${my_fqdn}) is not found in cluster members for role '${cluster_role}' in ${facts['country']}-${facts['region']}")
|
|
}
|
|
|
|
|
|
# Include sub-classes in dependency order
|
|
include stalwart::install
|
|
include stalwart::config
|
|
include stalwart::service
|
|
|
|
# Handle DNS records if requested
|
|
if $manage_dns_records {
|
|
if $loadbalancer_host {
|
|
# Only first node in cluster creates DNS records pointing to load balancer
|
|
if $my_fqdn == $sorted_cluster_members[0] {
|
|
class { 'stalwart::dns':
|
|
target_host => $loadbalancer_host,
|
|
}
|
|
}
|
|
} else {
|
|
# Current behavior: each server creates its own DNS records
|
|
include stalwart::dns
|
|
}
|
|
}
|
|
|
|
# Class ordering
|
|
Class['stalwart::install']
|
|
-> Class['stalwart::config']
|
|
-> Class['stalwart::service']
|
|
|
|
if $manage_dns_records {
|
|
Class['stalwart::service'] -> Class['stalwart::dns']
|
|
}
|
|
}
|