# @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'] } }