puppet-prod/modules/stalwart/README.md
Ben Vincent 0e890c7b56
All checks were successful
Build / precommit (pull_request) Successful in 5m42s
feat: create stalwart module
- add stalwart module
- add psql database on the shared patroni instance
- add ceph-rgw credentials to eyaml
- ensure psql pass and s3 access key are converted to sensitive
2025-11-08 17:04:55 +11:00

7.2 KiB

Stalwart Mail Server Module

This Puppet module manages Stalwart Mail Server, a modern, secure, and scalable mail server implementation that supports IMAP, JMAP, WebDAV, and SMTP protocols.

Overview

The stalwart module provides a comprehensive solution for deploying Stalwart Mail Server in a clustered environment with:

  • PostgreSQL backend for data, full-text search, and in-memory storage
  • S3/Ceph-RGW backend for blob storage (emails, attachments, sieve scripts)
  • Automatic cluster discovery using query_nodes()
  • DNS autodiscovery records for email client configuration
  • TLS certificate management integration
  • Postfix relay integration for SMTP routing

Features

  • Multi-node clustering with peer-to-peer coordination
  • PostgreSQL authentication with SQL directory backend
  • S3 blob storage with compression support
  • IMAP/IMAPS protocols for email access
  • HTTP/HTTPS protocols for JMAP, WebDAV, and autodiscovery
  • SMTP relay for postfix integration
  • DNS autodiscovery record management
  • Automatic role distribution across cluster nodes
  • TLS security with Vault PKI integration

Requirements

  • Puppet 6+ with query_nodes() function support
  • Stalwart RPM package (creates user, directories, systemd service)
  • PostgreSQL cluster for data storage
  • S3-compatible storage (Ceph-RGW, MinIO, AWS S3)
  • DNS management via profiles::dns::record
  • PKI management via profiles::pki::vault::alt_names

Usage

The recommended way to use this module is via the roles::infra::mail::backend role with hieradata configuration:

include roles::infra::mail::backend

Configure all parameters in hieradata/roles/infra/mail/backend.yaml - see examples/role-hieradata.yaml for a complete example.

Direct Class Usage

class { 'stalwart':
  node_id             => 1,
  cluster_role        => 'mail-backend',
  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',
}

Hieradata Configuration

See examples/role-hieradata.yaml for a complete example of role-based hieradata configuration.

Required Parameters

# Cluster role for node discovery
stalwart::cluster_role: 'mail-backend'

# Optional: Unique node identifier (auto-calculated if not specified)
# stalwart::node_id: 1

# PostgreSQL connection
stalwart::postgresql_host: 'pgsql.example.com'
stalwart::postgresql_database: 'stalwart'
stalwart::postgresql_user: 'stalwart'
stalwart::postgresql_password: >
  ENC[PKCS7,encrypted_password...]  

# S3/Ceph-RGW connection
stalwart::s3_endpoint: 'https://ceph-rgw.example.com'
stalwart::s3_bucket: 'stalwart-blobs'
stalwart::s3_access_key: 'access_key'
stalwart::s3_secret_key: >
  ENC[PKCS7,encrypted_secret...]  

# Domains and relay
stalwart::domains:
  - 'example.com'
stalwart::postfix_relay_host: 'postfix.example.com'

Architecture

Cluster Setup

The module automatically discovers cluster members using query_nodes() based on:

  • enc_role matching cluster_role parameter
  • country fact matching the node's country fact
  • region fact matching the node's region fact

Node ID Assignment:

  • Node IDs are automatically extracted from the last 4 digits of the hostname
  • Example: ausyd1nxvm1234 → node ID 1234
  • Manual override available via stalwart::node_id parameter if needed
  • Hostname must end with 4 digits for automatic extraction to work
  • Ensures unique IDs when following consistent hostname patterns

Storage Layout

  • Data Store: PostgreSQL (metadata, folders, settings)
  • Full-Text Search: PostgreSQL (search indexes)
  • In-Memory Store: PostgreSQL (caching, sessions)
  • Blob Store: S3/Ceph-RGW (emails, attachments, files)

Directory Structure (Created by RPM)

  • Config: /opt/stalwart/etc/config.toml
  • Data: /var/lib/stalwart/ (queue, reports)
  • Logs: /var/log/stalwart/stalwart.log
  • Binary: /opt/stalwart/bin/stalwart
  • User: stalwart:stalwart (system user)

Network Ports

  • 143: IMAP (STARTTLS)
  • 993: IMAPS (implicit TLS)
  • 443: HTTPS (JMAP, WebDAV, autodiscovery)
  • 2525: SMTP relay (postfix communication)
  • 11200: Cluster coordination (peer-to-peer)
  • 9090: Prometheus metrics

DNS Records

When manage_dns_records: true, the module creates:

  • autoconfig.domain.com → server FQDN (Thunderbird)
  • autodiscover.domain.com → server FQDN (Outlook)
  • _imap._tcp.domain.com SRV record
  • _imaps._tcp.domain.com SRV record
  • _caldav._tcp.domain.com SRV record
  • _carddav._tcp.domain.com SRV record

PostgreSQL Schema

The module expects these tables in the PostgreSQL database:

CREATE TABLE accounts (
  name TEXT PRIMARY KEY,
  secret TEXT,
  description TEXT,
  type TEXT NOT NULL,
  quota INTEGER DEFAULT 0,
  active BOOLEAN DEFAULT true
);

CREATE TABLE group_members (
  name TEXT NOT NULL,
  member_of TEXT NOT NULL,
  PRIMARY KEY (name, member_of)
);

CREATE TABLE emails (
  name TEXT NOT NULL,
  address TEXT NOT NULL,
  type TEXT,
  PRIMARY KEY (name, address)
);

Security

  • TLS required for all connections
  • PostgreSQL SSL enabled by default
  • S3 HTTPS endpoints required
  • Password hashing supported (SHA512, BCRYPT, etc.)
  • Certificate management via Vault PKI

Fallback Administrator

Stalwart includes a fallback administrator account for initial setup and emergency access:

  • Default username: admin (configurable via stalwart::fallback_admin_user)
  • Default password: admin (configurable via stalwart::fallback_admin_password)
  • Purpose: Initial server configuration and emergency access when directory services are unavailable
  • Security: Password is automatically hashed using SHA-512 crypt format

Important: Change the default password in production by setting different hieradata values:

stalwart::fallback_admin_password: "your-secure-password"

The fallback admin should only be used for initial setup and emergencies. Create regular admin accounts in PostgreSQL for day-to-day management.

Monitoring

  • Prometheus metrics on port 9090
  • Log files in /var/log/stalwart/
  • Queue monitoring in /var/lib/stalwart/queue/
  • Service status via systemd (stalwart.service)

Troubleshooting

Cluster Formation Issues

  • Verify query_nodes() returns expected nodes
  • Check country and region facts are consistent
  • Ensure cluster_role matches across all nodes

Storage Connection Issues

  • Test PostgreSQL connectivity and credentials
  • Verify S3 endpoint accessibility and credentials
  • Check network connectivity between nodes

TLS Certificate Issues

  • Ensure PKI alt_names include all required domains
  • Verify certificate paths exist and are readable
  • Check certificate expiration dates

License

This module is part of the internal infrastructure management system.